New composition area with emoji typeahead
|
@ -947,16 +947,6 @@
|
|||
"message": "File icon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "سيجنال للحاسوب. مرحبا بك ",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "File icon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Добре дошли в Сигнал за настолен компютър",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Icona del fitxer",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Imatge emoji de «$title$»",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Us donem la benvinguda al Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "File icon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Vítejte v aplikaci Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Filikon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji billede af '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Velkommen til Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Dateisymbol",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emojibild von »$title$«",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Willkommen bei Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Εικονίδιο αρχείου",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Εικόνα emoji '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Καλώς ορίσατε στο Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -1067,16 +1067,6 @@
|
|||
"description":
|
||||
"Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Welcome to Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Dosierpiktogramo",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoĝibildo de „$title$“",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Bonvenon al Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Icono de archivo",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji representando '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Bienvenida a Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -811,16 +811,6 @@
|
|||
"message": "File icon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Bienvenido a Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Faili ikoon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "$title$ emoji-pilt",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Tere tulemast Signal Desktopi kasutama",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "آیکون فایل",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "تصویر ایموجی '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "به Signal Desktop خوشآمدید",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Tiedoston ikoni",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji kuva: '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Tervetuloa Signal Desktopiin",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Icône de fichier",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Image émoji de « $title$ »",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Bienvenue sur Signal Desktop pour ordinateur",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "צלמית קובץ",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "תמונת אימוג'י של '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "ברוך הבא אל Signal Desktop עבודה",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "File icon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Signal डेस्कटॉप में आपका स्वागत है",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "File icon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Dobrodošli u Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Fájl ikon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "'$title$'-t ábrázoló emoji",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Üdvözöl a Signal Desktop!",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Ikon Berkas",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Gambar emoji dari '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Selamat datang di Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Icona del file",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji immagine di '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Benvenuto in Signal per Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "ファイルのアイコン",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "「$title$」の絵文字画像",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Signal Desktopにようこそ",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "រូបតំណាងឯកសារ",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "រូបEmoji របស់ '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "ស្វាគមន៍មកកាន់ Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "File icon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Signal ಗಣಕತೆರೆಗೆ ಸ್ವಾಗತ",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "File icon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Welcome to Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Failo piktograma",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Šypsenėlė \"$title$\"",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Sveiki atvykę į Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "File icon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Welcome to Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Filikon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji-bilde av «$title$»",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Velkommen til Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Bestandspictogram",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji-afbeelding ‘$title$’",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Welkom bij Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Filikon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji-bilde av «$title$»",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Velkommen til Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Filikon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji-bilde av «$title$»",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Velkommen til Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Ikona pliku",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Ikonka emoji '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Witamy w Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Ícone do arquivo",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Boas-vindas ao Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Ícone do ficheiro",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji de '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "O Signal Desktop dá-lhe as boas-vindas!",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Icoană fișier",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Imagine emoji pentru '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Bun venit la Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Значок файла",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Добро пожаловать в Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Ikona súboru",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji \"$title$\"",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Vitajte v aplikácii Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Ikona datoteke",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji znak za '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Dobrodošli v aplikaciji Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Ikonë kartele",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Figurë emoji e '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Mirë se vini te Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "File icon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Добродошли у Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Filikon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emojibild av '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Välkommen till Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "ไอคอนไฟล์",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "รูปอีโมจิของ '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "ยินดีต้อนรับสู่ Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "Dosya ikonu",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "'$title$' emoji resmi",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Signal Desktop'a Hoş Geldiniz",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "File icon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Ласкаво просимо до Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "File icon",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "Emoji image of '$title$'",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "Chào mừng đến với Signal Desktop",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "文件图标",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": "“$title$”的表情图片",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "欢迎来到Signal桌面版",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -947,16 +947,6 @@
|
|||
"message": "檔案圖示",
|
||||
"description": "Used in the media gallery documents tab to visually represent a file"
|
||||
},
|
||||
"emojiAlt": {
|
||||
"message": " '$title$' 的表情符號圖片",
|
||||
"description": "Used in the alt tag of all emoji images",
|
||||
"placeholders": {
|
||||
"title": {
|
||||
"content": "$1",
|
||||
"example": "grinning"
|
||||
}
|
||||
}
|
||||
},
|
||||
"installWelcome": {
|
||||
"message": "歡迎來到 Signal Desktop版",
|
||||
"description": "Welcome title on the install page"
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
<title>Signal</title>
|
||||
<link href='images/icon_128.png' rel='shortcut icon'>
|
||||
<link href="stylesheets/manifest.css" rel="stylesheet" type="text/css" />
|
||||
<link href="node_modules/draft-js/dist/Draft.css" rel="stylesheet" type="text/css" />
|
||||
|
||||
<!--
|
||||
When making changes to these templates, be sure to update test/index.html as well
|
||||
|
@ -116,9 +117,7 @@
|
|||
<div class='compose'>
|
||||
<form class='send clearfix file-input'>
|
||||
<div class='flex'>
|
||||
<div class='emoji-button-placeholder'></div>
|
||||
<textarea class='send-message' placeholder='{{ send-message }}' rows='1' dir='auto'></textarea>
|
||||
<div class='sticker-button-placeholder'></div>
|
||||
<div class='composition-area-placeholder'></div>
|
||||
<div class='capture-audio'>
|
||||
<button class='microphone'></button>
|
||||
</div>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
"license": "GPLV3",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"autosize": "~4.0.0",
|
||||
"indexeddb-backbonejs-adapter": "*",
|
||||
"mp3lameencoder": "https://github.com/higuma/mp3-lame-encoder-js.git",
|
||||
"protobuf": "~3.8.0",
|
||||
|
@ -16,9 +15,6 @@
|
|||
"mock-socket": "~0.3.2"
|
||||
},
|
||||
"preen": {
|
||||
"autosize": [
|
||||
"dist/autosize.js"
|
||||
],
|
||||
"bytebuffer": [
|
||||
"dist/ByteBufferAB.js"
|
||||
],
|
||||
|
|
292
components/autosize/dist/autosize.js
vendored
|
@ -1,292 +0,0 @@
|
|||
/*!
|
||||
Autosize 4.0.0
|
||||
license: MIT
|
||||
http://www.jacklmoore.com/autosize
|
||||
*/
|
||||
(function (global, factory) {
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(['exports', 'module'], factory);
|
||||
} else if (typeof exports !== 'undefined' && typeof module !== 'undefined') {
|
||||
factory(exports, module);
|
||||
} else {
|
||||
var mod = {
|
||||
exports: {}
|
||||
};
|
||||
factory(mod.exports, mod);
|
||||
global.autosize = mod.exports;
|
||||
}
|
||||
})(this, function (exports, module) {
|
||||
'use strict';
|
||||
|
||||
var map = typeof Map === "function" ? new Map() : (function () {
|
||||
var keys = [];
|
||||
var values = [];
|
||||
|
||||
return {
|
||||
has: function has(key) {
|
||||
return keys.indexOf(key) > -1;
|
||||
},
|
||||
get: function get(key) {
|
||||
return values[keys.indexOf(key)];
|
||||
},
|
||||
set: function set(key, value) {
|
||||
if (keys.indexOf(key) === -1) {
|
||||
keys.push(key);
|
||||
values.push(value);
|
||||
}
|
||||
},
|
||||
'delete': function _delete(key) {
|
||||
var index = keys.indexOf(key);
|
||||
if (index > -1) {
|
||||
keys.splice(index, 1);
|
||||
values.splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
})();
|
||||
|
||||
var createEvent = function createEvent(name) {
|
||||
return new Event(name, { bubbles: true });
|
||||
};
|
||||
try {
|
||||
new Event('test');
|
||||
} catch (e) {
|
||||
// IE does not support `new Event()`
|
||||
createEvent = function (name) {
|
||||
var evt = document.createEvent('Event');
|
||||
evt.initEvent(name, true, false);
|
||||
return evt;
|
||||
};
|
||||
}
|
||||
|
||||
function assign(ta) {
|
||||
if (!ta || !ta.nodeName || ta.nodeName !== 'TEXTAREA' || map.has(ta)) return;
|
||||
|
||||
var heightOffset = null;
|
||||
var clientWidth = ta.clientWidth;
|
||||
var cachedHeight = null;
|
||||
|
||||
function init() {
|
||||
var style = window.getComputedStyle(ta, null);
|
||||
|
||||
if (style.resize === 'vertical') {
|
||||
ta.style.resize = 'none';
|
||||
} else if (style.resize === 'both') {
|
||||
ta.style.resize = 'horizontal';
|
||||
}
|
||||
|
||||
if (style.boxSizing === 'content-box') {
|
||||
heightOffset = -(parseFloat(style.paddingTop) + parseFloat(style.paddingBottom));
|
||||
} else {
|
||||
heightOffset = parseFloat(style.borderTopWidth) + parseFloat(style.borderBottomWidth);
|
||||
}
|
||||
// Fix when a textarea is not on document body and heightOffset is Not a Number
|
||||
if (isNaN(heightOffset)) {
|
||||
heightOffset = 0;
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
function changeOverflow(value) {
|
||||
{
|
||||
// Chrome/Safari-specific fix:
|
||||
// When the textarea y-overflow is hidden, Chrome/Safari do not reflow the text to account for the space
|
||||
// made available by removing the scrollbar. The following forces the necessary text reflow.
|
||||
var width = ta.style.width;
|
||||
ta.style.width = '0px';
|
||||
// Force reflow:
|
||||
/* jshint ignore:start */
|
||||
ta.offsetWidth;
|
||||
/* jshint ignore:end */
|
||||
ta.style.width = width;
|
||||
}
|
||||
|
||||
ta.style.overflowY = value;
|
||||
}
|
||||
|
||||
function getParentOverflows(el) {
|
||||
var arr = [];
|
||||
|
||||
while (el && el.parentNode && el.parentNode instanceof Element) {
|
||||
if (el.parentNode.scrollTop) {
|
||||
arr.push({
|
||||
node: el.parentNode,
|
||||
scrollTop: el.parentNode.scrollTop
|
||||
});
|
||||
}
|
||||
el = el.parentNode;
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
||||
function resize() {
|
||||
var originalHeight = ta.style.height;
|
||||
var overflows = getParentOverflows(ta);
|
||||
var docTop = document.documentElement && document.documentElement.scrollTop; // Needed for Mobile IE (ticket #240)
|
||||
|
||||
ta.style.height = '';
|
||||
|
||||
var endHeight = ta.scrollHeight + heightOffset;
|
||||
|
||||
if (ta.scrollHeight === 0) {
|
||||
// If the scrollHeight is 0, then the element probably has display:none or is detached from the DOM.
|
||||
ta.style.height = originalHeight;
|
||||
return;
|
||||
}
|
||||
|
||||
ta.style.height = endHeight + 'px';
|
||||
|
||||
// used to check if an update is actually necessary on window.resize
|
||||
clientWidth = ta.clientWidth;
|
||||
|
||||
// prevents scroll-position jumping
|
||||
overflows.forEach(function (el) {
|
||||
el.node.scrollTop = el.scrollTop;
|
||||
});
|
||||
|
||||
if (docTop) {
|
||||
document.documentElement.scrollTop = docTop;
|
||||
}
|
||||
}
|
||||
|
||||
function update() {
|
||||
resize();
|
||||
|
||||
var styleHeight = Math.round(parseFloat(ta.style.height));
|
||||
var computed = window.getComputedStyle(ta, null);
|
||||
|
||||
// Using offsetHeight as a replacement for computed.height in IE, because IE does not account use of border-box
|
||||
var actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(computed.height)) : ta.offsetHeight;
|
||||
|
||||
// The actual height not matching the style height (set via the resize method) indicates that
|
||||
// the max-height has been exceeded, in which case the overflow should be allowed.
|
||||
if (actualHeight !== styleHeight) {
|
||||
if (computed.overflowY === 'hidden') {
|
||||
changeOverflow('scroll');
|
||||
resize();
|
||||
actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight;
|
||||
}
|
||||
} else {
|
||||
// Normally keep overflow set to hidden, to avoid flash of scrollbar as the textarea expands.
|
||||
if (computed.overflowY !== 'hidden') {
|
||||
changeOverflow('hidden');
|
||||
resize();
|
||||
actualHeight = computed.boxSizing === 'content-box' ? Math.round(parseFloat(window.getComputedStyle(ta, null).height)) : ta.offsetHeight;
|
||||
}
|
||||
}
|
||||
|
||||
if (cachedHeight !== actualHeight) {
|
||||
cachedHeight = actualHeight;
|
||||
var evt = createEvent('autosize:resized');
|
||||
try {
|
||||
ta.dispatchEvent(evt);
|
||||
} catch (err) {
|
||||
// Firefox will throw an error on dispatchEvent for a detached element
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=889376
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var pageResize = function pageResize() {
|
||||
if (ta.clientWidth !== clientWidth) {
|
||||
update();
|
||||
}
|
||||
};
|
||||
|
||||
var destroy = (function (style) {
|
||||
window.removeEventListener('resize', pageResize, false);
|
||||
ta.removeEventListener('input', update, false);
|
||||
ta.removeEventListener('keyup', update, false);
|
||||
ta.removeEventListener('autosize:destroy', destroy, false);
|
||||
ta.removeEventListener('autosize:update', update, false);
|
||||
|
||||
Object.keys(style).forEach(function (key) {
|
||||
ta.style[key] = style[key];
|
||||
});
|
||||
|
||||
map['delete'](ta);
|
||||
}).bind(ta, {
|
||||
height: ta.style.height,
|
||||
resize: ta.style.resize,
|
||||
overflowY: ta.style.overflowY,
|
||||
overflowX: ta.style.overflowX,
|
||||
wordWrap: ta.style.wordWrap
|
||||
});
|
||||
|
||||
ta.addEventListener('autosize:destroy', destroy, false);
|
||||
|
||||
// IE9 does not fire onpropertychange or oninput for deletions,
|
||||
// so binding to onkeyup to catch most of those events.
|
||||
// There is no way that I know of to detect something like 'cut' in IE9.
|
||||
if ('onpropertychange' in ta && 'oninput' in ta) {
|
||||
ta.addEventListener('keyup', update, false);
|
||||
}
|
||||
|
||||
window.addEventListener('resize', pageResize, false);
|
||||
ta.addEventListener('input', update, false);
|
||||
ta.addEventListener('autosize:update', update, false);
|
||||
ta.style.overflowX = 'hidden';
|
||||
ta.style.wordWrap = 'break-word';
|
||||
|
||||
map.set(ta, {
|
||||
destroy: destroy,
|
||||
update: update
|
||||
});
|
||||
|
||||
init();
|
||||
}
|
||||
|
||||
function destroy(ta) {
|
||||
var methods = map.get(ta);
|
||||
if (methods) {
|
||||
methods.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
function update(ta) {
|
||||
var methods = map.get(ta);
|
||||
if (methods) {
|
||||
methods.update();
|
||||
}
|
||||
}
|
||||
|
||||
var autosize = null;
|
||||
|
||||
// Do nothing in Node.js environment and IE8 (or lower)
|
||||
if (typeof window === 'undefined' || typeof window.getComputedStyle !== 'function') {
|
||||
autosize = function (el) {
|
||||
return el;
|
||||
};
|
||||
autosize.destroy = function (el) {
|
||||
return el;
|
||||
};
|
||||
autosize.update = function (el) {
|
||||
return el;
|
||||
};
|
||||
} else {
|
||||
autosize = function (el, options) {
|
||||
if (el) {
|
||||
Array.prototype.forEach.call(el.length ? el : [el], function (x) {
|
||||
return assign(x, options);
|
||||
});
|
||||
}
|
||||
return el;
|
||||
};
|
||||
autosize.destroy = function (el) {
|
||||
if (el) {
|
||||
Array.prototype.forEach.call(el.length ? el : [el], destroy);
|
||||
}
|
||||
return el;
|
||||
};
|
||||
autosize.update = function (el) {
|
||||
if (el) {
|
||||
Array.prototype.forEach.call(el.length ? el : [el], update);
|
||||
}
|
||||
return el;
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = autosize;
|
||||
});
|
8
images/emoji-object-filled-20.svg
Executable file → Normal file
|
@ -1,7 +1 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Export" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<path d="M16.5,7.5C16.5,3.9,13.6,1,10,1C6.4,1,3.5,3.9,3.5,7.5c0,1.5,0.5,2.9,1.4,4.1l0,0l0,0v0.1c1.4,1.6,2.6,2.9,2.6,4.4v1.5
|
||||
c0,0.6,0.4,1.2,1,1.5h3c0.6-0.3,1-0.9,1-1.5H9V16h3.5c0-1.5,1.2-2.8,2.5-4.3v-0.1l0,0l0,0C16,10.5,16.5,9,16.5,7.5z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>emoji-object-solid-20</title><path d="M10,1A6.487,6.487,0,0,0,3.5,7.5a6.773,6.773,0,0,0,1.4,4.1S7.49,13.987,7.49,16v1.5A1.5,1.5,0,0,0,8.99,19h2a1.5,1.5,0,0,0,1.5-1.5V16h.01c0-2,2.5-4.4,2.5-4.4a6.1,6.1,0,0,0,1.5-4.1A6.487,6.487,0,0,0,10,1Zm1,16.5H9v-2h2Z"/></svg>
|
Before Width: | Height: | Size: 591 B After Width: | Height: | Size: 352 B |
9
images/emoji-object-outline-20.svg
Executable file → Normal file
|
@ -1,8 +1 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Export" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<path d="M10,1C6.4,1,3.5,3.9,3.5,7.5c0,1.5,0.5,2.9,1.4,4.1c1.3,1.7,2.6,2.9,2.6,4.4v1.5c0,0.6,0.4,1.2,1,1.5h3c0.6-0.3,1-0.9,1-1.5
|
||||
H9V16h3.5c0-1.5,1.2-2.8,2.6-4.4c2.3-2.8,1.8-6.9-1-9.1C12.9,1.5,11.5,1,10,1z M8.7,14.5c-0.5-1.2-1.2-2.2-2.1-3.2l-0.5-0.7
|
||||
C5.4,9.8,5,8.6,5,7.5c0-2.8,2.2-5,5-5s5,2.2,5,5c0,1.2-0.4,2.3-1.1,3.2l-0.5,0.6c-0.9,0.9-1.6,2-2.1,3.2H8.7z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>emoji-object-outline-20</title><path d="M14.1,2.5A6.291,6.291,0,0,0,10,1,6.487,6.487,0,0,0,3.5,7.5a6.773,6.773,0,0,0,1.4,4.1c1.3,1.7,2.6,2.9,2.6,4.4v1.5A1.5,1.5,0,0,0,9,19h2a1.5,1.5,0,0,0,1.5-1.5V16c0-1.5,1.2-2.8,2.6-4.4A6.4,6.4,0,0,0,14.1,2.5Zm-3.09,15h-2V16h2Zm2.89-6.8a25.578,25.578,0,0,0-2.6,3.8H8.7a28.167,28.167,0,0,0-2.6-3.9A4.887,4.887,0,0,1,5,7.5a5,5,0,0,1,10,0A5.167,5.167,0,0,1,13.9,10.7Z"/></svg>
|
Before Width: | Height: | Size: 711 B After Width: | Height: | Size: 498 B |
9
images/emoji-symbol-filled-20.svg
Executable file → Normal file
|
@ -1,8 +1 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Export" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<path d="M18.9,5.8c-0.2-0.9-0.6-1.7-1.3-2.4c-1.9-1.9-4.9-1.9-6.7,0c-0.3,0.3-0.6,0.7-0.9,1c-0.3-0.4-0.6-0.7-0.9-1
|
||||
c-1.9-1.9-4.9-1.9-6.7,0C1.7,4,1.3,4.8,1.1,5.7C1,6,1,6.3,1,6.7c0,4.6,5.1,9.1,9,12.4c4-3.2,9-7.8,9-12.4C19,6.4,19,6.1,18.9,5.8z"
|
||||
/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>emoji-symbol-solid-20</title><path d="M16,2H4A3,3,0,0,0,1,5V15a3,3,0,0,0,3,3H16a3,3,0,0,0,3-3V5A3,3,0,0,0,16,2Zm-.829,6.911H12.793l-.464,2.178h2.077V12.54h-2.38L11.5,15H10.011l.523-2.46H8.4L7.873,15H6.381l.524-2.46H4.829V11.089H7.207l.464-2.178H5.594V7.46h2.38L8.5,5H9.99L9.466,7.46H11.6L12.127,5h1.492L13.1,7.46h2.076Zm-6.008,0H11.3l-.464,2.178H8.7Z"/></svg>
|
Before Width: | Height: | Size: 596 B After Width: | Height: | Size: 449 B |
11
images/emoji-symbol-outline-20.svg
Executable file → Normal file
|
@ -1,10 +1 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Export" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 20 20" style="enable-background:new 0 0 20 20;" xml:space="preserve">
|
||||
<path d="M14.2,3.5c1.6,0,2.9,1.1,3.2,2.6c0,0.2,0.1,0.4,0.1,0.5c0,3.6-4,7.5-7.5,10.4c-2.8-2.3-7.5-6.6-7.5-10.4
|
||||
c0-0.2,0-0.4,0.1-0.6l0,0C2.7,5.5,3,4.9,3.4,4.5c1.3-1.3,3.3-1.3,4.6,0l0,0l0,0C8.3,4.7,8.5,5,8.7,5.3L10,7.1l1.2-1.9
|
||||
c0.2-0.3,0.4-0.5,0.6-0.8l0,0l0,0l0,0C12.5,3.9,13.3,3.5,14.2,3.5 M14.2,2c-1.3,0-2.5,0.5-3.4,1.4c-0.3,0.3-0.6,0.7-0.8,1
|
||||
c-0.3-0.4-0.6-0.7-0.9-1c-1.9-1.9-4.9-1.9-6.7,0C1.7,4,1.3,4.8,1.1,5.7C1,6,1,6.3,1,6.7c0,4.6,5.1,9.1,9,12.4c4-3.2,9-7.8,9-12.4
|
||||
c0-0.3,0-0.6-0.1-0.9C18.4,3.6,16.5,2,14.2,2L14.2,2z"/>
|
||||
</svg>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 20 20"><title>emoji-symbol-outline-20</title><path d="M16,3.5A1.5,1.5,0,0,1,17.5,5V15A1.5,1.5,0,0,1,16,16.5H4A1.5,1.5,0,0,1,2.5,15V5A1.5,1.5,0,0,1,4,3.5H16M16,2H4A3,3,0,0,0,1,5V15a3,3,0,0,0,3,3H16a3,3,0,0,0,3-3V5a3,3,0,0,0-3-3ZM10.011,15l.523-2.46H8.4L7.873,15H6.381l.524-2.46H4.829V11.089H7.207l.464-2.178H5.594V7.46h2.38L8.5,5H9.99L9.466,7.46H11.6L12.127,5h1.492L13.1,7.46h2.076V8.911H12.793l-.464,2.178h2.077V12.54h-2.38L11.5,15ZM11.3,8.911H9.163L8.7,11.089h2.138Z"/></svg>
|
Before Width: | Height: | Size: 877 B After Width: | Height: | Size: 552 B |
|
@ -74,11 +74,10 @@ const {
|
|||
} = require('../../ts/components/conversation/VerificationNotification');
|
||||
|
||||
// State
|
||||
const { createEmojiButton } = require('../../ts/state/roots/createEmojiButton');
|
||||
const { createLeftPane } = require('../../ts/state/roots/createLeftPane');
|
||||
const {
|
||||
createStickerButton,
|
||||
} = require('../../ts/state/roots/createStickerButton');
|
||||
createCompositionArea,
|
||||
} = require('../../ts/state/roots/createCompositionArea');
|
||||
const { createLeftPane } = require('../../ts/state/roots/createLeftPane');
|
||||
const {
|
||||
createStickerManager,
|
||||
} = require('../../ts/state/roots/createStickerManager');
|
||||
|
@ -286,9 +285,8 @@ exports.setup = (options = {}) => {
|
|||
};
|
||||
|
||||
const Roots = {
|
||||
createEmojiButton,
|
||||
createCompositionArea,
|
||||
createLeftPane,
|
||||
createStickerButton,
|
||||
createStickerManager,
|
||||
createStickerPreviewModal,
|
||||
};
|
||||
|
|
|
@ -92,6 +92,7 @@
|
|||
this.listenTo(this.model, 'change:verified', this.onVerifiedChange);
|
||||
this.listenTo(this.model, 'newmessage', this.addMessage);
|
||||
this.listenTo(this.model, 'opened', this.onOpened);
|
||||
this.listenTo(this.model, 'backgrounded', this.resetEmojiResults);
|
||||
this.listenTo(this.model, 'prune', this.onPrune);
|
||||
this.listenTo(this.model, 'unload', () => this.unload('model trigger'));
|
||||
this.listenTo(this.model, 'typing-update', this.renderTypingBubble);
|
||||
|
@ -200,11 +201,6 @@
|
|||
this.$('.discussion-container').append(this.view.el);
|
||||
this.view.render();
|
||||
|
||||
this.$messageField = this.$('.send-message');
|
||||
|
||||
this.onResize = this.forceUpdateMessageFieldSize.bind(this);
|
||||
this.window.addEventListener('resize', this.onResize);
|
||||
|
||||
this.onFocus = () => {
|
||||
if (this.$el.css('display') !== 'none') {
|
||||
this.markRead();
|
||||
|
@ -222,36 +218,22 @@
|
|||
this.$('.send-message').blur(this.unfocusBottomBar.bind(this));
|
||||
|
||||
this.setupHeader();
|
||||
this.setupEmojiPickerButton();
|
||||
this.setupStickerPickerButton();
|
||||
|
||||
this.lastSelectionStart = 0;
|
||||
document.addEventListener(
|
||||
'selectionchange',
|
||||
this.updateLastSelectionStart.bind(this, undefined)
|
||||
);
|
||||
this.setupCompositionArea();
|
||||
},
|
||||
|
||||
events: {
|
||||
'submit .send': 'clickSend',
|
||||
'input .send-message': 'updateMessageFieldSize',
|
||||
'keydown .send-message': 'updateMessageFieldSize',
|
||||
'keyup .send-message': 'onKeyUp',
|
||||
click: 'onClick',
|
||||
'click .emoji-button-placeholder': 'onClickPlaceholder',
|
||||
'click .sticker-button-placeholder': 'onClickPlaceholder',
|
||||
'click .composition-area-placeholder': 'onClickPlaceholder',
|
||||
'click .bottom-bar': 'focusMessageField',
|
||||
'click .capture-audio .microphone': 'captureAudio',
|
||||
'click .module-scroll-down': 'scrollToBottom',
|
||||
'focus .send-message': 'focusBottomBar',
|
||||
'change .file-input': 'toggleMicrophone',
|
||||
'blur .send-message': 'unfocusBottomBar',
|
||||
'loadMore .message-list': 'loadMoreMessages',
|
||||
'newOffscreenMessage .message-list': 'addScrollDownButtonWithCount',
|
||||
'atBottom .message-list': 'removeScrollDownButton',
|
||||
'farFromBottom .message-list': 'addScrollDownButton',
|
||||
'lazyScroll .message-list': 'onLazyScroll',
|
||||
'force-resize': 'forceUpdateMessageFieldSize',
|
||||
|
||||
'click button.paperclip': 'onChooseAttachment',
|
||||
'change input.file-input': 'onChoseAttachment',
|
||||
|
@ -331,52 +313,31 @@
|
|||
this.$('.conversation-header').append(this.titleView.el);
|
||||
},
|
||||
|
||||
setupEmojiPickerButton() {
|
||||
const props = {
|
||||
onForceSend: () => {
|
||||
this.sendMessage({});
|
||||
},
|
||||
onPickEmoji: e => this.insertEmoji(e),
|
||||
onClose: () => {
|
||||
const textarea = this.$messageField[0];
|
||||
|
||||
textarea.focus();
|
||||
|
||||
const newPos = textarea.value.length;
|
||||
textarea.selectionStart = newPos;
|
||||
textarea.selectionEnd = newPos;
|
||||
|
||||
this.forceUpdateLastSelectionStart(newPos);
|
||||
},
|
||||
};
|
||||
|
||||
this.emojiButtonView = new Whisper.ReactWrapperView({
|
||||
className: 'emoji-button-wrapper',
|
||||
JSX: Signal.State.Roots.createEmojiButton(window.reduxStore, props),
|
||||
});
|
||||
|
||||
// Finally, add it to the DOM
|
||||
this.$('.emoji-button-placeholder').append(this.emojiButtonView.el);
|
||||
},
|
||||
|
||||
setupStickerPickerButton() {
|
||||
if (!window.ENABLE_STICKER_SEND) {
|
||||
return;
|
||||
}
|
||||
setupCompositionArea() {
|
||||
const compositionApi = { current: null };
|
||||
this.compositionApi = compositionApi;
|
||||
|
||||
const props = {
|
||||
compositionApi,
|
||||
onClickAddPack: () => this.showStickerManager(),
|
||||
onPickSticker: (packId, stickerId) =>
|
||||
this.sendStickerMessage({ packId, stickerId }),
|
||||
onSubmit: message => this.sendMessage(message),
|
||||
onDirtyChange: dirty => this.toggleMicrophone(dirty),
|
||||
onEditorStateChange: (msg, caretLocation) =>
|
||||
this.onEditorStateChange(msg, caretLocation),
|
||||
onEditorSizeChange: rect => this.onEditorSizeChange(rect),
|
||||
};
|
||||
|
||||
this.stickerButtonView = new Whisper.ReactWrapperView({
|
||||
className: 'sticker-button-wrapper',
|
||||
JSX: Signal.State.Roots.createStickerButton(window.reduxStore, props),
|
||||
this.compositionAreaView = new Whisper.ReactWrapperView({
|
||||
className: 'composition-area-wrapper',
|
||||
JSX: Signal.State.Roots.createCompositionArea(window.reduxStore, props),
|
||||
});
|
||||
|
||||
// Finally, add it to the DOM
|
||||
this.$('.sticker-button-placeholder').append(this.stickerButtonView.el);
|
||||
this.$('.composition-area-placeholder').append(
|
||||
this.compositionAreaView.el
|
||||
);
|
||||
},
|
||||
|
||||
// We need this, or clicking the reactified buttons will submit the form and send any
|
||||
|
@ -479,14 +440,7 @@
|
|||
}
|
||||
}
|
||||
|
||||
this.window.removeEventListener('resize', this.onResize);
|
||||
this.window.removeEventListener('focus', this.onFocus);
|
||||
document.removeEventListener(
|
||||
'selectionchange',
|
||||
this.updateLastSelectionStart
|
||||
);
|
||||
|
||||
window.autosize.destroy(this.$messageField);
|
||||
|
||||
this.view.remove();
|
||||
|
||||
|
@ -628,11 +582,8 @@
|
|||
}
|
||||
},
|
||||
|
||||
toggleMicrophone() {
|
||||
if (
|
||||
this.$('.send-message').val().length > 0 ||
|
||||
this.fileInput.hasFiles()
|
||||
) {
|
||||
toggleMicrophone(dirty = false) {
|
||||
if (dirty || this.fileInput.hasFiles()) {
|
||||
this.$('.capture-audio').hide();
|
||||
} else {
|
||||
this.$('.capture-audio').show();
|
||||
|
@ -664,7 +615,7 @@
|
|||
view.on('closed', this.endCaptureAudio.bind(this));
|
||||
view.$el.appendTo(this.$('.capture-audio'));
|
||||
|
||||
this.$('.send-message').attr('disabled', true);
|
||||
this.disableMessageField();
|
||||
this.$('.microphone').hide();
|
||||
},
|
||||
handleAudioCapture(blob) {
|
||||
|
@ -673,10 +624,10 @@
|
|||
file: blob,
|
||||
isVoiceNote: true,
|
||||
});
|
||||
this.$('.bottom-bar form').submit();
|
||||
this.sendMessage();
|
||||
},
|
||||
endCaptureAudio() {
|
||||
this.$('.send-message').removeAttr('disabled');
|
||||
this.enableMessageField();
|
||||
this.$('.microphone').show();
|
||||
this.captureAudioView = null;
|
||||
},
|
||||
|
@ -745,7 +696,6 @@
|
|||
messagesLoaded.then(this.onLoaded.bind(this), this.onLoaded.bind(this));
|
||||
|
||||
this.view.resetScrollPosition();
|
||||
this.$el.trigger('force-resize');
|
||||
this.focusMessageField();
|
||||
this.renderTypingBubble();
|
||||
|
||||
|
@ -1088,13 +1038,28 @@
|
|||
return;
|
||||
}
|
||||
|
||||
this.$messageField.focus();
|
||||
const { compositionApi } = this;
|
||||
|
||||
if (compositionApi && compositionApi.current) {
|
||||
compositionApi.current.focusInput();
|
||||
}
|
||||
},
|
||||
|
||||
focusMessageFieldAndClearDisabled() {
|
||||
this.$messageField.removeAttr('disabled');
|
||||
this.$messageField.focus();
|
||||
this.updateLastSelectionStart();
|
||||
this.compositionApi.current.setDisabled(false);
|
||||
this.focusMessageField();
|
||||
},
|
||||
|
||||
disableMessageField() {
|
||||
this.compositionApi.current.setDisabled(true);
|
||||
},
|
||||
|
||||
enableMessageField() {
|
||||
this.compositionApi.current.setDisabled(false);
|
||||
},
|
||||
|
||||
resetEmojiResults() {
|
||||
this.compositionApi.current.resetEmojiResults(false);
|
||||
},
|
||||
|
||||
async loadMoreMessages() {
|
||||
|
@ -1648,7 +1613,6 @@
|
|||
view.remove();
|
||||
|
||||
if (this.panels.length === 0) {
|
||||
this.$el.trigger('force-resize');
|
||||
// Make sure poppers are positioned properly
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
}
|
||||
|
@ -1716,36 +1680,6 @@
|
|||
});
|
||||
},
|
||||
|
||||
async clickSend(e, options) {
|
||||
e.preventDefault();
|
||||
|
||||
this.sendStart = Date.now();
|
||||
this.$messageField.attr('disabled', true);
|
||||
|
||||
try {
|
||||
const contacts = await this.getUntrustedContacts(options);
|
||||
|
||||
if (contacts && contacts.length) {
|
||||
const sendAnyway = await this.showSendAnywayDialog(contacts);
|
||||
if (sendAnyway) {
|
||||
this.clickSend(e, { force: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this.focusMessageFieldAndClearDisabled();
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendMessage(e);
|
||||
} catch (error) {
|
||||
this.focusMessageFieldAndClearDisabled();
|
||||
window.log.error(
|
||||
'clickSend error:',
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
async sendStickerMessage(options = {}) {
|
||||
try {
|
||||
const contacts = await this.getUntrustedContacts(options);
|
||||
|
@ -1799,34 +1733,6 @@
|
|||
return null;
|
||||
},
|
||||
|
||||
insertEmoji({ shortName, skinTone }) {
|
||||
const skinReplacement = window.Signal.Emojis.hasVariation(
|
||||
shortName,
|
||||
skinTone
|
||||
)
|
||||
? `:skin-tone-${skinTone}:`
|
||||
: '';
|
||||
|
||||
const colons = `:${shortName}:${skinReplacement}`;
|
||||
|
||||
const textarea = this.$messageField[0];
|
||||
const hasFocus = document.activeElement === textarea;
|
||||
const startPos = hasFocus
|
||||
? textarea.selectionStart
|
||||
: this.lastSelectionStart;
|
||||
const endPos = hasFocus ? textarea.selectionEnd : this.lastSelectionStart;
|
||||
|
||||
textarea.value =
|
||||
textarea.value.substring(0, startPos) +
|
||||
colons +
|
||||
textarea.value.substring(endPos, textarea.value.length);
|
||||
const newPos = startPos + colons.length;
|
||||
textarea.selectionStart = newPos;
|
||||
textarea.selectionEnd = newPos;
|
||||
this.forceUpdateLastSelectionStart(newPos);
|
||||
this.forceUpdateMessageFieldSize({});
|
||||
},
|
||||
|
||||
async setQuoteMessage(messageId) {
|
||||
this.quote = null;
|
||||
this.quotedMessage = null;
|
||||
|
@ -1858,7 +1764,6 @@
|
|||
}
|
||||
if (!this.quotedMessage) {
|
||||
this.view.restoreBottomOffset();
|
||||
this.updateMessageFieldSize({});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -1890,18 +1795,39 @@
|
|||
}),
|
||||
onInitialRender: () => {
|
||||
this.view.restoreBottomOffset();
|
||||
this.updateMessageFieldSize({});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
async sendMessage(e) {
|
||||
async sendMessage(message = '', options = {}) {
|
||||
this.sendStart = Date.now();
|
||||
|
||||
try {
|
||||
const contacts = await this.getUntrustedContacts(options);
|
||||
this.disableMessageField();
|
||||
|
||||
if (contacts && contacts.length) {
|
||||
const sendAnyway = await this.showSendAnywayDialog(contacts);
|
||||
if (sendAnyway) {
|
||||
this.sendMessage(message, { force: true });
|
||||
return;
|
||||
}
|
||||
|
||||
this.focusMessageFieldAndClearDisabled();
|
||||
return;
|
||||
}
|
||||
} catch (error) {
|
||||
this.focusMessageFieldAndClearDisabled();
|
||||
window.log.error(
|
||||
'sendMessage error:',
|
||||
error && error.stack ? error.stack : error
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
this.removeLastSeenIndicator();
|
||||
this.model.clearTypingTimers();
|
||||
|
||||
const input = this.$messageField;
|
||||
const message = window.Signal.Emojis.replaceColons(input.val()).trim();
|
||||
|
||||
let toast;
|
||||
if (extension.expired()) {
|
||||
toast = new Whisper.ExpiredToast();
|
||||
|
@ -1942,11 +1868,9 @@
|
|||
this.getLinkPreview()
|
||||
);
|
||||
|
||||
input.val('');
|
||||
this.compositionApi.current.reset();
|
||||
this.setQuoteMessage(null);
|
||||
this.resetLinkPreview();
|
||||
this.focusMessageFieldAndClearDisabled();
|
||||
this.forceUpdateMessageFieldSize(e);
|
||||
this.fileInput.clearAttachments();
|
||||
} catch (error) {
|
||||
window.log.error(
|
||||
|
@ -1958,24 +1882,16 @@
|
|||
}
|
||||
},
|
||||
|
||||
onKeyUp() {
|
||||
this.maybeBumpTyping();
|
||||
this.debouncedMaybeGrabLinkPreview();
|
||||
onEditorStateChange(messageText, caretLocation) {
|
||||
this.maybeBumpTyping(messageText);
|
||||
this.debouncedMaybeGrabLinkPreview(messageText, caretLocation);
|
||||
},
|
||||
|
||||
updateLastSelectionStart(newPos) {
|
||||
if (document.activeElement === this.$messageField[0]) {
|
||||
this.forceUpdateLastSelectionStart(newPos);
|
||||
}
|
||||
onEditorSizeChange() {
|
||||
this.view.scrollToBottomIfNeeded();
|
||||
},
|
||||
|
||||
forceUpdateLastSelectionStart(
|
||||
newPos = this.$messageField[0].selectionStart
|
||||
) {
|
||||
this.lastSelectionStart = newPos;
|
||||
},
|
||||
|
||||
maybeGrabLinkPreview() {
|
||||
maybeGrabLinkPreview(message, caretLocation) {
|
||||
// Don't generate link previews if user has turned them off
|
||||
if (!storage.get('linkPreviews', false)) {
|
||||
return;
|
||||
|
@ -1993,10 +1909,7 @@
|
|||
return;
|
||||
}
|
||||
|
||||
const messageText = this.$messageField.val().trim();
|
||||
const caretLocation = this.$messageField.get(0).selectionStart;
|
||||
|
||||
if (!messageText) {
|
||||
if (!message) {
|
||||
this.resetLinkPreview();
|
||||
return;
|
||||
}
|
||||
|
@ -2005,7 +1918,7 @@
|
|||
}
|
||||
|
||||
const links = window.Signal.LinkPreviews.findLinks(
|
||||
messageText,
|
||||
message,
|
||||
caretLocation
|
||||
);
|
||||
const { currentlyMatchedLink } = this;
|
||||
|
@ -2310,7 +2223,6 @@
|
|||
}
|
||||
if (!this.currentlyMatchedLink) {
|
||||
this.view.restoreBottomOffset();
|
||||
this.updateMessageFieldSize({});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -2332,7 +2244,6 @@
|
|||
props,
|
||||
onInitialRender: () => {
|
||||
this.view.restoreBottomOffset();
|
||||
this.updateMessageFieldSize({});
|
||||
},
|
||||
});
|
||||
},
|
||||
|
@ -2362,59 +2273,12 @@
|
|||
|
||||
// Called whenever the user changes the message composition field. But only
|
||||
// fires if there's content in the message field after the change.
|
||||
maybeBumpTyping() {
|
||||
const messageText = this.$messageField.val();
|
||||
maybeBumpTyping(messageText) {
|
||||
if (messageText.length) {
|
||||
this.model.throttledBumpTyping();
|
||||
}
|
||||
},
|
||||
|
||||
updateMessageFieldSize(event) {
|
||||
const keyCode = event.which || event.keyCode;
|
||||
|
||||
if (
|
||||
keyCode === 13 &&
|
||||
!event.altKey &&
|
||||
!event.shiftKey &&
|
||||
!event.ctrlKey
|
||||
) {
|
||||
// enter pressed - submit the form now
|
||||
event.preventDefault();
|
||||
this.$('.bottom-bar form').submit();
|
||||
return;
|
||||
}
|
||||
this.toggleMicrophone();
|
||||
|
||||
this.view.measureScrollPosition();
|
||||
window.autosize(this.$messageField);
|
||||
|
||||
const $attachmentPreviews = this.$('.attachment-previews');
|
||||
const $bottomBar = this.$('.bottom-bar');
|
||||
const includeMargin = true;
|
||||
const quoteHeight = this.quoteView
|
||||
? this.quoteView.$el.outerHeight(includeMargin)
|
||||
: 0;
|
||||
|
||||
const height =
|
||||
this.$messageField.outerHeight() +
|
||||
$attachmentPreviews.outerHeight() +
|
||||
quoteHeight +
|
||||
parseInt($bottomBar.css('min-height'), 10);
|
||||
|
||||
$bottomBar.outerHeight(height);
|
||||
|
||||
this.view.scrollToBottomIfNeeded();
|
||||
},
|
||||
|
||||
forceUpdateMessageFieldSize(event) {
|
||||
if (this.isHidden()) {
|
||||
return;
|
||||
}
|
||||
this.view.scrollToBottomIfNeeded();
|
||||
window.autosize.update(this.$messageField);
|
||||
this.updateMessageFieldSize(event);
|
||||
},
|
||||
|
||||
isHidden() {
|
||||
return (
|
||||
this.$el.css('display') === 'none' ||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
|
||||
Whisper.ConversationStack = Whisper.View.extend({
|
||||
className: 'conversation-stack',
|
||||
lastConversation: null,
|
||||
open(conversation) {
|
||||
const id = `conversation-${conversation.cid}`;
|
||||
if (id !== this.el.firstChild.id) {
|
||||
|
@ -42,6 +43,10 @@
|
|||
$el.prependTo(this.el);
|
||||
}
|
||||
conversation.trigger('opened');
|
||||
if (this.lastConversation) {
|
||||
this.lastConversation.trigger('backgrounded');
|
||||
}
|
||||
this.lastConversation = conversation;
|
||||
// Make sure poppers are positioned properly
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
},
|
||||
|
|
|
@ -53,17 +53,19 @@
|
|||
"bunyan": "1.8.12",
|
||||
"classnames": "2.2.5",
|
||||
"config": "1.28.1",
|
||||
"draft-js": "0.10.5",
|
||||
"electron-context-menu": "0.11.0",
|
||||
"electron-editor-context-menu": "1.1.1",
|
||||
"electron-is-dev": "0.3.0",
|
||||
"emoji-datasource": "4.1.0",
|
||||
"emoji-datasource-apple": "4.1.0",
|
||||
"emoji-js": "3.4.0",
|
||||
"emoji-regex": "8.0.0",
|
||||
"filesize": "3.6.1",
|
||||
"firstline": "1.2.1",
|
||||
"form-data": "2.3.2",
|
||||
"fs-extra": "5.0.0",
|
||||
"fuse.js": "^3.4.4",
|
||||
"fuse.js": "3.4.4",
|
||||
"glob": "7.1.2",
|
||||
"google-libphonenumber": "3.2.2",
|
||||
"got": "8.2.0",
|
||||
|
@ -90,7 +92,8 @@
|
|||
"react": "16.8.3",
|
||||
"react-contextmenu": "2.11.0",
|
||||
"react-dom": "16.8.3",
|
||||
"react-popper": "^1.3.3",
|
||||
"react-measure": "2.3.0",
|
||||
"react-popper": "1.3.3",
|
||||
"react-redux": "6.0.1",
|
||||
"react-virtualized": "9.21.0",
|
||||
"read-last-lines": "1.3.0",
|
||||
|
@ -113,6 +116,7 @@
|
|||
"@types/chai": "4.1.2",
|
||||
"@types/classnames": "2.2.3",
|
||||
"@types/config": "0.0.34",
|
||||
"@types/draft-js": "0.10.32",
|
||||
"@types/filesize": "3.6.0",
|
||||
"@types/fs-extra": "5.0.5",
|
||||
"@types/google-libphonenumber": "7.4.14",
|
||||
|
@ -128,6 +132,7 @@
|
|||
"@types/qs": "6.5.1",
|
||||
"@types/react": "16.8.5",
|
||||
"@types/react-dom": "16.8.2",
|
||||
"@types/react-measure": "2.0.5",
|
||||
"@types/react-redux": "7.0.1",
|
||||
"@types/react-virtualized": "9.18.12",
|
||||
"@types/redux-logger": "3.0.7",
|
||||
|
|
|
@ -62,6 +62,11 @@ module.exports = {
|
|||
type: 'text/css',
|
||||
href: '/stylesheets/manifest.css',
|
||||
},
|
||||
{
|
||||
rel: 'stylesheet',
|
||||
type: 'text/css',
|
||||
href: '/node_modules/draft-js/dist/Draft.css',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -2988,6 +2988,23 @@
|
|||
.module-spinner__arc--small {
|
||||
-webkit-mask: url('../images/spinner-24.svg') no-repeat center;
|
||||
-webkit-mask-size: 100%;
|
||||
height: 24px;
|
||||
width: 24px;
|
||||
}
|
||||
|
||||
.module-spinner__container--mini {
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
.module-spinner__circle--mini {
|
||||
-webkit-mask: url('../images/spinner-track-24.svg') no-repeat center;
|
||||
-webkit-mask-size: 100%;
|
||||
height: 14px;
|
||||
width: 14px;
|
||||
}
|
||||
.module-spinner__arc--mini {
|
||||
-webkit-mask: url('../images/spinner-24.svg') no-repeat center;
|
||||
-webkit-mask-size: 100%;
|
||||
}
|
||||
|
||||
.module-spinner__circle--incoming {
|
||||
|
@ -4540,6 +4557,11 @@
|
|||
&--#{$size} {
|
||||
width: $size;
|
||||
height: $size;
|
||||
&--inline {
|
||||
display: inline-block;
|
||||
vertical-align: bottom;
|
||||
background-size: $size $size;
|
||||
}
|
||||
}
|
||||
&__image--#{$size} {
|
||||
width: $size;
|
||||
|
@ -4550,17 +4572,23 @@
|
|||
|
||||
.module-emoji {
|
||||
display: block;
|
||||
color: transparent;
|
||||
|
||||
@include light-theme() {
|
||||
caret-color: $color-gray-90;
|
||||
}
|
||||
|
||||
@include dark-theme() {
|
||||
caret-color: $color-gray-05;
|
||||
}
|
||||
|
||||
@include emoji-size(16px);
|
||||
@include emoji-size(18px);
|
||||
@include emoji-size(20px);
|
||||
@include emoji-size(28px);
|
||||
@include emoji-size(32px);
|
||||
@include emoji-size(64px);
|
||||
@include emoji-size(66px);
|
||||
|
||||
&--inline {
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
|
||||
// Module: Unsupported Message
|
||||
|
@ -4649,6 +4677,147 @@
|
|||
stroke-width: 2;
|
||||
}
|
||||
|
||||
// Module: CompositionInput
|
||||
.module-composition-input {
|
||||
&__input {
|
||||
line-height: 20px;
|
||||
border: 1px solid;
|
||||
border-radius: 18px;
|
||||
font-size: 14px;
|
||||
font-family: Roboto;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
|
||||
&__scroller {
|
||||
padding: 7px 12px;
|
||||
min-height: 32px;
|
||||
max-height: 80px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
@include light-theme() {
|
||||
border-color: $color-gray-15;
|
||||
background: $color-white;
|
||||
color: $color-gray-90;
|
||||
}
|
||||
|
||||
@include dark-theme() {
|
||||
border-color: $color-gray-60;
|
||||
background: $color-dark-85;
|
||||
color: $color-gray-05;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
@include light-theme() {
|
||||
border-color: $color-signal-blue;
|
||||
}
|
||||
|
||||
@include dark-theme() {
|
||||
border-color: $color-signal-blue;
|
||||
}
|
||||
}
|
||||
|
||||
// Override draft.js styles
|
||||
.public-DraftEditorPlaceholder-root {
|
||||
@include light-theme() {
|
||||
color: $color-gray-45;
|
||||
}
|
||||
|
||||
@include dark-theme() {
|
||||
color: $color-gray-45;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__emoji-suggestions {
|
||||
padding: 12px 0;
|
||||
margin-bottom: 6px;
|
||||
border-radius: 8px;
|
||||
z-index: 2;
|
||||
|
||||
@include popper-shadow();
|
||||
|
||||
@include light-theme() {
|
||||
background: $color-white;
|
||||
}
|
||||
|
||||
@include dark-theme() {
|
||||
background: $color-gray-75;
|
||||
}
|
||||
|
||||
&__row {
|
||||
height: 30px;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
background: none;
|
||||
border: none;
|
||||
width: 100%;
|
||||
font-size: 13px;
|
||||
font-family: Roboto;
|
||||
|
||||
@include light-theme() {
|
||||
color: $color-gray-60;
|
||||
}
|
||||
|
||||
@include dark-theme() {
|
||||
color: $color-gray-25;
|
||||
}
|
||||
|
||||
&__short-name {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
&--selected,
|
||||
&:hover {
|
||||
@include light-theme() {
|
||||
background: $color-gray-10;
|
||||
color: $color-gray-90;
|
||||
}
|
||||
|
||||
@include dark-theme() {
|
||||
background: $color-gray-60;
|
||||
color: $color-gray-05;
|
||||
}
|
||||
}
|
||||
}
|
||||
stroke: $color-white;
|
||||
}
|
||||
}
|
||||
|
||||
// Module: CompositionArea
|
||||
.module-composition-area {
|
||||
// Layout
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
// Child Elements
|
||||
&__button-cell {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 44px;
|
||||
height: 100%;
|
||||
flex-shrink: 0;
|
||||
&--microphone-active {
|
||||
width: 100px;
|
||||
}
|
||||
}
|
||||
&__input {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.composition-area-placeholder {
|
||||
flex-grow: 1;
|
||||
margin: {
|
||||
top: 3px;
|
||||
bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
// Third-party module: react-contextmenu
|
||||
|
||||
.react-contextmenu {
|
||||
|
|
29
ts/components/CompositionArea.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
#### Default
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<div style={{ minHeight: '500px', paddingTop: '450px' }}>
|
||||
<CompositionArea
|
||||
i18n={util.i18n}
|
||||
onSubmit={s => console.log('onSubmit', s)}
|
||||
onDirtyChange={dirty =>
|
||||
console.log(`Dirty Change: ${dirty ? 'dirty' : 'not dirty'}`)
|
||||
}
|
||||
// EmojiButton
|
||||
onSetSkinTone={s => console.log('onSetSkinTone', s)}
|
||||
// StickerButton
|
||||
knownPacks={[]}
|
||||
receivedPacks={[]}
|
||||
installedPacks={[]}
|
||||
blessedPacks={[]}
|
||||
recentStickers={[]}
|
||||
clearInstalledStickerPack={() => console.log('clearInstalledStickerPack')}
|
||||
onClickAddPack={(...args) => console.log('onClickAddPack', ...args)}
|
||||
onPickSticker={(...args) => console.log('onPickSticker', ...args)}
|
||||
clearShowIntroduction={() => console.log('clearShowIntroduction')}
|
||||
showPickerHint={false}
|
||||
clearShowPickerHint={() => console.log('clearShowIntroduction')}
|
||||
/>
|
||||
</div>
|
||||
</util.ConversationContext>
|
||||
```
|
182
ts/components/CompositionArea.tsx
Normal file
|
@ -0,0 +1,182 @@
|
|||
import * as React from 'react';
|
||||
import { Editor } from 'draft-js';
|
||||
import {
|
||||
EmojiButton,
|
||||
EmojiPickDataType,
|
||||
Props as EmojiButtonProps,
|
||||
} from './emoji/EmojiButton';
|
||||
import {
|
||||
Props as StickerButtonProps,
|
||||
StickerButton,
|
||||
} from './stickers/StickerButton';
|
||||
import {
|
||||
CompositionInput,
|
||||
InputApi,
|
||||
Props as CompositionInputProps,
|
||||
} from './CompositionInput';
|
||||
import { countStickers } from './stickers/lib';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
|
||||
export type OwnProps = {
|
||||
readonly i18n: LocalizerType;
|
||||
readonly compositionApi?: React.MutableRefObject<{
|
||||
focusInput: () => void;
|
||||
setDisabled: (disabled: boolean) => void;
|
||||
reset: InputApi['reset'];
|
||||
resetEmojiResults: InputApi['resetEmojiResults'];
|
||||
}>;
|
||||
};
|
||||
|
||||
export type Props = CompositionInputProps &
|
||||
Pick<
|
||||
EmojiButtonProps,
|
||||
'onPickEmoji' | 'onSetSkinTone' | 'recentEmojis' | 'skinTone'
|
||||
> &
|
||||
Pick<
|
||||
StickerButtonProps,
|
||||
| 'knownPacks'
|
||||
| 'receivedPacks'
|
||||
| 'installedPacks'
|
||||
| 'blessedPacks'
|
||||
| 'recentStickers'
|
||||
| 'clearInstalledStickerPack'
|
||||
| 'onClickAddPack'
|
||||
| 'onPickSticker'
|
||||
| 'clearShowIntroduction'
|
||||
| 'showPickerHint'
|
||||
| 'clearShowPickerHint'
|
||||
> &
|
||||
OwnProps;
|
||||
|
||||
// tslint:disable-next-line max-func-body-length
|
||||
export const CompositionArea = ({
|
||||
i18n,
|
||||
// CompositionInput
|
||||
onDirtyChange,
|
||||
onSubmit,
|
||||
compositionApi,
|
||||
onEditorSizeChange,
|
||||
onEditorStateChange,
|
||||
// EmojiButton
|
||||
onPickEmoji,
|
||||
onSetSkinTone,
|
||||
recentEmojis,
|
||||
skinTone,
|
||||
// StickerButton
|
||||
knownPacks,
|
||||
receivedPacks,
|
||||
installedPacks,
|
||||
blessedPacks,
|
||||
recentStickers,
|
||||
clearInstalledStickerPack,
|
||||
onClickAddPack,
|
||||
onPickSticker,
|
||||
clearShowIntroduction,
|
||||
showPickerHint,
|
||||
clearShowPickerHint,
|
||||
}: Props) => {
|
||||
const [disabled, setDisabled] = React.useState(false);
|
||||
const editorRef = React.useRef<Editor>(null);
|
||||
const inputApiRef = React.useRef<InputApi | undefined>();
|
||||
|
||||
const handleForceSend = React.useCallback(
|
||||
() => {
|
||||
if (inputApiRef.current) {
|
||||
inputApiRef.current.submit();
|
||||
}
|
||||
},
|
||||
[inputApiRef]
|
||||
);
|
||||
|
||||
const focusInput = React.useCallback(
|
||||
() => {
|
||||
if (editorRef.current) {
|
||||
editorRef.current.focus();
|
||||
}
|
||||
},
|
||||
[editorRef]
|
||||
);
|
||||
|
||||
const withStickers =
|
||||
countStickers({
|
||||
knownPacks,
|
||||
blessedPacks,
|
||||
installedPacks,
|
||||
receivedPacks,
|
||||
}) > 0;
|
||||
|
||||
if (compositionApi) {
|
||||
compositionApi.current = {
|
||||
focusInput,
|
||||
setDisabled,
|
||||
reset: () => {
|
||||
if (inputApiRef.current) {
|
||||
inputApiRef.current.reset();
|
||||
}
|
||||
},
|
||||
resetEmojiResults: () => {
|
||||
if (inputApiRef.current) {
|
||||
inputApiRef.current.resetEmojiResults();
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const insertEmoji = React.useCallback(
|
||||
(e: EmojiPickDataType) => {
|
||||
if (inputApiRef.current) {
|
||||
inputApiRef.current.insertEmoji(e);
|
||||
onPickEmoji(e);
|
||||
}
|
||||
},
|
||||
[inputApiRef, onPickEmoji]
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="module-composition-area">
|
||||
<div className="module-composition-area__button-cell">
|
||||
<EmojiButton
|
||||
i18n={i18n}
|
||||
doSend={handleForceSend}
|
||||
onPickEmoji={insertEmoji}
|
||||
recentEmojis={recentEmojis}
|
||||
skinTone={skinTone}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
onClose={focusInput}
|
||||
/>
|
||||
</div>
|
||||
<div className="module-composition-area__input">
|
||||
<CompositionInput
|
||||
i18n={i18n}
|
||||
disabled={disabled}
|
||||
editorRef={editorRef}
|
||||
inputApi={inputApiRef}
|
||||
onPickEmoji={onPickEmoji}
|
||||
onSubmit={onSubmit}
|
||||
onEditorSizeChange={onEditorSizeChange}
|
||||
onEditorStateChange={onEditorStateChange}
|
||||
onDirtyChange={onDirtyChange}
|
||||
skinTone={skinTone}
|
||||
/>
|
||||
</div>
|
||||
{withStickers ? (
|
||||
<div className="module-composition-area__button-cell">
|
||||
<StickerButton
|
||||
i18n={i18n}
|
||||
knownPacks={knownPacks}
|
||||
receivedPacks={receivedPacks}
|
||||
installedPacks={installedPacks}
|
||||
blessedPacks={blessedPacks}
|
||||
recentStickers={recentStickers}
|
||||
clearInstalledStickerPack={clearInstalledStickerPack}
|
||||
onClickAddPack={onClickAddPack}
|
||||
onPickSticker={onPickSticker}
|
||||
clearShowIntroduction={clearShowIntroduction}
|
||||
showPickerHint={showPickerHint}
|
||||
clearShowPickerHint={clearShowPickerHint}
|
||||
/>
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
);
|
||||
};
|
12
ts/components/CompositionInput.md
Normal file
|
@ -0,0 +1,12 @@
|
|||
#### Default
|
||||
|
||||
```jsx
|
||||
<util.ConversationContext theme={util.theme}>
|
||||
<div style={{ minHeight: '500px', paddingTop: '450px' }}>
|
||||
<CompositionInput
|
||||
i18n={util.i18n}
|
||||
onSubmit={s => console.log('onSubmit', s)}
|
||||
/>
|
||||
</div>
|
||||
</util.ConversationContext>
|
||||
```
|
693
ts/components/CompositionInput.tsx
Normal file
|
@ -0,0 +1,693 @@
|
|||
import * as React from 'react';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { createSelector } from 'reselect';
|
||||
import {
|
||||
CompositeDecorator,
|
||||
ContentBlock,
|
||||
ContentState,
|
||||
DraftEditorCommand,
|
||||
DraftHandleValue,
|
||||
Editor,
|
||||
EditorChangeType,
|
||||
EditorState,
|
||||
getDefaultKeyBinding,
|
||||
Modifier,
|
||||
SelectionState,
|
||||
} from 'draft-js';
|
||||
import Measure, { ContentRect } from 'react-measure';
|
||||
import { Manager, Popper, Reference } from 'react-popper';
|
||||
import { clamp, noop } from 'lodash';
|
||||
import classNames from 'classnames';
|
||||
import emojiRegex from 'emoji-regex';
|
||||
import { Emoji } from './emoji/Emoji';
|
||||
import { EmojiPickDataType } from './emoji/EmojiPicker';
|
||||
import {
|
||||
convertShortName,
|
||||
EmojiData,
|
||||
replaceColons,
|
||||
search,
|
||||
} from './emoji/lib';
|
||||
import { LocalizerType } from '../types/Util';
|
||||
|
||||
const colonsRegex = /(?:^|\s):[a-z0-9-_+]+:?/gi;
|
||||
|
||||
export type Props = {
|
||||
readonly i18n: LocalizerType;
|
||||
readonly disabled?: boolean;
|
||||
readonly editorRef?: React.RefObject<Editor>;
|
||||
readonly inputApi?: React.MutableRefObject<InputApi | undefined>;
|
||||
readonly skinTone?: EmojiPickDataType['skinTone'];
|
||||
onDirtyChange?(dirty: boolean): unknown;
|
||||
onEditorStateChange?(messageText: string, caretLocation: number): unknown;
|
||||
onEditorSizeChange?(rect: ContentRect): unknown;
|
||||
onPickEmoji(o: EmojiPickDataType): unknown;
|
||||
onSubmit(message: string): unknown;
|
||||
};
|
||||
|
||||
export type InputApi = {
|
||||
insertEmoji: (e: EmojiPickDataType) => void;
|
||||
reset: () => void;
|
||||
resetEmojiResults: () => void;
|
||||
submit: () => void;
|
||||
};
|
||||
|
||||
export type CompositionInputEditorCommand =
|
||||
| DraftEditorCommand
|
||||
| ('enter-emoji' | 'next-emoji' | 'prev-emoji' | 'submit');
|
||||
|
||||
function getTrimmedMatchAtIndex(str: string, index: number, pattern: RegExp) {
|
||||
let match;
|
||||
|
||||
// Reset regex state
|
||||
pattern.exec('');
|
||||
|
||||
// tslint:disable-next-line no-conditional-assignment
|
||||
while ((match = pattern.exec(str))) {
|
||||
const matchStr = match.toString();
|
||||
const start = match.index + (matchStr.length - matchStr.trimLeft().length);
|
||||
const end = match.index + matchStr.trimRight().length;
|
||||
|
||||
if (index >= start && index <= end) {
|
||||
return match.toString();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
function getWordAtIndex(str: string, index: number) {
|
||||
const start = str
|
||||
.slice(0, index + 1)
|
||||
.replace(/\s+$/, '')
|
||||
.search(/\S+$/);
|
||||
const end = str.slice(index).search(/(?:\s|$)/) + index;
|
||||
|
||||
return {
|
||||
start,
|
||||
end,
|
||||
word: str.slice(start, end),
|
||||
};
|
||||
}
|
||||
|
||||
const compositeDecorator = new CompositeDecorator([
|
||||
{
|
||||
strategy: (block, cb) => {
|
||||
const pat = emojiRegex();
|
||||
const text = block.getText();
|
||||
let match;
|
||||
let index;
|
||||
// tslint:disable-next-line no-conditional-assignment
|
||||
while ((match = pat.exec(text))) {
|
||||
index = match.index;
|
||||
cb(index, index + match[0].length);
|
||||
}
|
||||
},
|
||||
component: ({
|
||||
children,
|
||||
contentState,
|
||||
entityKey,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
contentState: ContentState;
|
||||
entityKey: string;
|
||||
}) =>
|
||||
entityKey ? (
|
||||
<Emoji
|
||||
shortName={contentState.getEntity(entityKey).getData().shortName}
|
||||
skinTone={contentState.getEntity(entityKey).getData().skinTone}
|
||||
inline={true}
|
||||
size={20}
|
||||
>
|
||||
{children}
|
||||
</Emoji>
|
||||
) : (
|
||||
children
|
||||
),
|
||||
},
|
||||
]);
|
||||
|
||||
type FunctionRef = (el: HTMLElement | null) => unknown;
|
||||
|
||||
// A selector which combines multiple react refs into a single, referentially-equal functional ref.
|
||||
const combineRefs = createSelector(
|
||||
(r1: FunctionRef) => r1,
|
||||
(_r1: any, r2: FunctionRef) => r2,
|
||||
(_r1: any, _r2: any, r3: React.MutableRefObject<HTMLDivElement>) => r3,
|
||||
(r1, r2, r3) => (el: HTMLDivElement) => {
|
||||
r1(el);
|
||||
r2(el);
|
||||
r3.current = el;
|
||||
}
|
||||
);
|
||||
|
||||
// tslint:disable-next-line max-func-body-length
|
||||
export const CompositionInput = ({
|
||||
i18n,
|
||||
disabled,
|
||||
editorRef,
|
||||
inputApi,
|
||||
onDirtyChange,
|
||||
onEditorStateChange,
|
||||
onEditorSizeChange,
|
||||
onPickEmoji,
|
||||
onSubmit,
|
||||
skinTone,
|
||||
}: Props) => {
|
||||
const [editorState, setEditorState] = React.useState(
|
||||
EditorState.createEmpty(compositeDecorator)
|
||||
);
|
||||
const [searchText, setSearchText] = React.useState<string>('');
|
||||
const [emojiResults, setEmojiResults] = React.useState<Array<EmojiData>>([]);
|
||||
const [emojiResultsIndex, setEmojiResultsIndex] = React.useState<number>(0);
|
||||
const [editorWidth, setEditorWidth] = React.useState<number>(0);
|
||||
const [popperRoot, setPopperRoot] = React.useState<HTMLDivElement | null>(
|
||||
null
|
||||
);
|
||||
const dirtyRef = React.useRef(false);
|
||||
const focusRef = React.useRef(false);
|
||||
const editorStateRef = React.useRef<EditorState>(editorState);
|
||||
const rootElRef = React.useRef<HTMLDivElement>();
|
||||
|
||||
// This function sets editorState and also keeps a reference to the newly set
|
||||
// state so we can reference the state in effects and callbacks without
|
||||
// excessive cleanup
|
||||
const setAndTrackEditorState = React.useCallback(
|
||||
(newState: EditorState) => {
|
||||
setEditorState(newState);
|
||||
editorStateRef.current = newState;
|
||||
},
|
||||
[setEditorState, editorStateRef]
|
||||
);
|
||||
|
||||
const updateExternalStateListeners = React.useCallback(
|
||||
(newState: EditorState) => {
|
||||
const plainText = newState.getCurrentContent().getPlainText();
|
||||
const currentBlockKey = newState.getSelection().getStartKey();
|
||||
const currentBlockIndex = editorState
|
||||
.getCurrentContent()
|
||||
.getBlockMap()
|
||||
.keySeq()
|
||||
.findIndex(key => key === currentBlockKey);
|
||||
const caretLocation = newState
|
||||
.getCurrentContent()
|
||||
.getBlockMap()
|
||||
.valueSeq()
|
||||
.toArray()
|
||||
.reduce((sum: number, block: ContentBlock, index: number) => {
|
||||
if (currentBlockIndex < index) {
|
||||
return sum + block.getText().length + 1; // +1 for newline
|
||||
}
|
||||
|
||||
if (currentBlockIndex === index) {
|
||||
return sum + newState.getSelection().getStartOffset();
|
||||
}
|
||||
|
||||
return sum;
|
||||
}, 0);
|
||||
|
||||
if (onDirtyChange) {
|
||||
const isDirty = !!plainText;
|
||||
if (dirtyRef.current !== isDirty) {
|
||||
dirtyRef.current = isDirty;
|
||||
onDirtyChange(isDirty);
|
||||
}
|
||||
}
|
||||
if (onEditorStateChange) {
|
||||
onEditorStateChange(plainText, caretLocation);
|
||||
}
|
||||
},
|
||||
[onDirtyChange, onEditorStateChange]
|
||||
);
|
||||
|
||||
const resetEmojiResults = React.useCallback(
|
||||
() => {
|
||||
setEmojiResults([]);
|
||||
setEmojiResultsIndex(0);
|
||||
setSearchText('');
|
||||
},
|
||||
[setEmojiResults, setEmojiResultsIndex, setSearchText]
|
||||
);
|
||||
|
||||
const handleEditorStateChange = React.useCallback(
|
||||
(newState: EditorState) => {
|
||||
// Does the current position have any emojiable text?
|
||||
const selection = newState.getSelection();
|
||||
const caretLocation = selection.getStartOffset();
|
||||
const content = newState
|
||||
.getCurrentContent()
|
||||
.getBlockForKey(selection.getAnchorKey())
|
||||
.getText();
|
||||
const match = getTrimmedMatchAtIndex(content, caretLocation, colonsRegex);
|
||||
|
||||
// Update the state to indicate emojiable text at the current position.
|
||||
const newSearchText = match ? match.trim().substr(1) : '';
|
||||
if (newSearchText.length >= 2 && focusRef.current) {
|
||||
setEmojiResults(search(newSearchText, 10));
|
||||
setSearchText(newSearchText);
|
||||
setEmojiResultsIndex(0);
|
||||
} else {
|
||||
resetEmojiResults();
|
||||
}
|
||||
|
||||
// Finally, update the editor state
|
||||
setAndTrackEditorState(newState);
|
||||
updateExternalStateListeners(newState);
|
||||
},
|
||||
[
|
||||
focusRef,
|
||||
resetEmojiResults,
|
||||
setAndTrackEditorState,
|
||||
setSearchText,
|
||||
setEmojiResults,
|
||||
]
|
||||
);
|
||||
|
||||
const resetEditorState = React.useCallback(
|
||||
() => {
|
||||
const newEmptyState = EditorState.createEmpty(compositeDecorator);
|
||||
setAndTrackEditorState(newEmptyState);
|
||||
resetEmojiResults();
|
||||
},
|
||||
[editorStateRef, resetEmojiResults, setAndTrackEditorState]
|
||||
);
|
||||
|
||||
const submit = React.useCallback(
|
||||
() => {
|
||||
const text = editorState.getCurrentContent().getPlainText();
|
||||
const emojidText = replaceColons(text);
|
||||
onSubmit(emojidText);
|
||||
},
|
||||
[editorState, onSubmit]
|
||||
);
|
||||
|
||||
const handleEditorSizeChange = React.useCallback(
|
||||
(rect: ContentRect) => {
|
||||
if (rect.bounds) {
|
||||
setEditorWidth(rect.bounds.width);
|
||||
if (onEditorSizeChange) {
|
||||
onEditorSizeChange(rect);
|
||||
}
|
||||
}
|
||||
},
|
||||
[onEditorSizeChange, setEditorWidth]
|
||||
);
|
||||
|
||||
const selectEmojiResult = React.useCallback(
|
||||
(dir: 'next' | 'prev', e?: React.KeyboardEvent) => {
|
||||
if (emojiResults.length > 0) {
|
||||
if (e) {
|
||||
e.preventDefault();
|
||||
}
|
||||
|
||||
if (dir === 'next') {
|
||||
setEmojiResultsIndex(
|
||||
clamp(emojiResultsIndex + 1, 0, emojiResults.length - 1)
|
||||
);
|
||||
}
|
||||
|
||||
if (dir === 'prev') {
|
||||
setEmojiResultsIndex(
|
||||
clamp(emojiResultsIndex - 1, 0, emojiResults.length - 1)
|
||||
);
|
||||
}
|
||||
}
|
||||
},
|
||||
[setEmojiResultsIndex, emojiResultsIndex, emojiResults]
|
||||
);
|
||||
|
||||
const handleEditorArrowKey = React.useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.key === 'ArrowUp') {
|
||||
selectEmojiResult('prev', e);
|
||||
}
|
||||
|
||||
if (e.key === 'ArrowDown') {
|
||||
selectEmojiResult('next', e);
|
||||
}
|
||||
},
|
||||
[selectEmojiResult]
|
||||
);
|
||||
|
||||
const handleEscapeKey = React.useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (emojiResults.length > 0) {
|
||||
e.preventDefault();
|
||||
resetEmojiResults();
|
||||
}
|
||||
},
|
||||
[resetEmojiResults, emojiResults]
|
||||
);
|
||||
|
||||
const getWordAtCaret = React.useCallback(
|
||||
() => {
|
||||
const selection = editorState.getSelection();
|
||||
const index = selection.getAnchorOffset();
|
||||
|
||||
return getWordAtIndex(
|
||||
editorState
|
||||
.getCurrentContent()
|
||||
.getBlockForKey(selection.getAnchorKey())
|
||||
.getText(),
|
||||
index
|
||||
);
|
||||
},
|
||||
[editorState]
|
||||
);
|
||||
|
||||
const insertEmoji = React.useCallback(
|
||||
(e: EmojiPickDataType, replaceWord: boolean = false) => {
|
||||
const selection = editorState.getSelection();
|
||||
const oldContent = editorState.getCurrentContent();
|
||||
const emojiContent = convertShortName(e.shortName, e.skinTone);
|
||||
const emojiEntityKey = oldContent
|
||||
.createEntity('emoji', 'IMMUTABLE', {
|
||||
shortName: e.shortName,
|
||||
skinTone: e.skinTone,
|
||||
})
|
||||
.getLastCreatedEntityKey();
|
||||
const word = getWordAtCaret();
|
||||
|
||||
let newContent = replaceWord
|
||||
? Modifier.replaceText(
|
||||
oldContent,
|
||||
selection.merge({
|
||||
anchorOffset: word.start,
|
||||
focusOffset: word.end,
|
||||
}) as SelectionState,
|
||||
emojiContent,
|
||||
undefined,
|
||||
emojiEntityKey
|
||||
)
|
||||
: Modifier.insertText(
|
||||
oldContent,
|
||||
selection,
|
||||
emojiContent,
|
||||
undefined,
|
||||
emojiEntityKey
|
||||
);
|
||||
|
||||
const afterSelection = newContent.getSelectionAfter();
|
||||
|
||||
if (
|
||||
afterSelection.getAnchorOffset() ===
|
||||
newContent.getBlockForKey(afterSelection.getAnchorKey()).getLength()
|
||||
) {
|
||||
newContent = Modifier.insertText(newContent, afterSelection, ' ');
|
||||
}
|
||||
|
||||
const newState = EditorState.push(
|
||||
editorState,
|
||||
newContent,
|
||||
'insert-emoji' as EditorChangeType
|
||||
);
|
||||
setAndTrackEditorState(newState);
|
||||
resetEmojiResults();
|
||||
},
|
||||
[editorState, setAndTrackEditorState, resetEmojiResults]
|
||||
);
|
||||
|
||||
const handleEditorCommand = React.useCallback(
|
||||
(
|
||||
command: CompositionInputEditorCommand,
|
||||
state: EditorState
|
||||
): DraftHandleValue => {
|
||||
if (command === 'enter-emoji') {
|
||||
const shortName = emojiResults[emojiResultsIndex].short_name;
|
||||
|
||||
const content = state.getCurrentContent();
|
||||
const selection = state.getSelection();
|
||||
const word = getWordAtCaret();
|
||||
const emojiContent = convertShortName(shortName, skinTone);
|
||||
const emojiEntityKey = content
|
||||
.createEntity('emoji', 'IMMUTABLE', {
|
||||
shortName,
|
||||
skinTone,
|
||||
})
|
||||
.getLastCreatedEntityKey();
|
||||
|
||||
const replaceSelection = selection.merge({
|
||||
anchorOffset: word.start,
|
||||
focusOffset: word.end,
|
||||
});
|
||||
|
||||
let newContent = Modifier.replaceText(
|
||||
content,
|
||||
replaceSelection as SelectionState,
|
||||
emojiContent,
|
||||
undefined,
|
||||
emojiEntityKey
|
||||
);
|
||||
|
||||
const afterSelection = newContent.getSelectionAfter();
|
||||
|
||||
if (
|
||||
afterSelection.getAnchorOffset() ===
|
||||
newContent.getBlockForKey(afterSelection.getAnchorKey()).getLength()
|
||||
) {
|
||||
newContent = Modifier.insertText(newContent, afterSelection, ' ');
|
||||
}
|
||||
|
||||
const newState = EditorState.push(
|
||||
state,
|
||||
newContent,
|
||||
'insert-emoji' as EditorChangeType
|
||||
);
|
||||
setAndTrackEditorState(newState);
|
||||
resetEmojiResults();
|
||||
onPickEmoji({ shortName });
|
||||
|
||||
return 'handled';
|
||||
}
|
||||
|
||||
if (command === 'submit') {
|
||||
submit();
|
||||
|
||||
return 'handled';
|
||||
}
|
||||
|
||||
if (command === 'next-emoji') {
|
||||
selectEmojiResult('next');
|
||||
}
|
||||
|
||||
if (command === 'prev-emoji') {
|
||||
selectEmojiResult('prev');
|
||||
}
|
||||
|
||||
return 'not-handled';
|
||||
},
|
||||
[
|
||||
emojiResults,
|
||||
emojiResultsIndex,
|
||||
resetEmojiResults,
|
||||
selectEmojiResult,
|
||||
setAndTrackEditorState,
|
||||
skinTone,
|
||||
submit,
|
||||
]
|
||||
);
|
||||
|
||||
const onTab = React.useCallback(
|
||||
(e: React.KeyboardEvent) => {
|
||||
if (e.shiftKey || emojiResults.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
handleEditorCommand('enter-emoji', editorState);
|
||||
},
|
||||
[emojiResults, editorState, handleEditorCommand, resetEmojiResults]
|
||||
);
|
||||
|
||||
const editorKeybindingFn = React.useCallback(
|
||||
(e: React.KeyboardEvent): CompositionInputEditorCommand | null => {
|
||||
if (e.key === 'Enter' && emojiResults.length > 0) {
|
||||
e.preventDefault();
|
||||
|
||||
return 'enter-emoji';
|
||||
}
|
||||
|
||||
if (e.key === 'Enter' && !e.shiftKey) {
|
||||
e.preventDefault();
|
||||
|
||||
return 'submit';
|
||||
}
|
||||
|
||||
if (e.key === 'n' && e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
|
||||
return 'next-emoji';
|
||||
}
|
||||
|
||||
if (e.key === 'p' && e.ctrlKey) {
|
||||
e.preventDefault();
|
||||
|
||||
return 'prev-emoji';
|
||||
}
|
||||
|
||||
return getDefaultKeyBinding(e);
|
||||
},
|
||||
[emojiResults]
|
||||
);
|
||||
|
||||
// Create popper root
|
||||
React.useEffect(
|
||||
() => {
|
||||
if (emojiResults.length > 0) {
|
||||
const root = document.createElement('div');
|
||||
setPopperRoot(root);
|
||||
document.body.appendChild(root);
|
||||
|
||||
return () => {
|
||||
document.body.removeChild(root);
|
||||
setPopperRoot(null);
|
||||
};
|
||||
}
|
||||
|
||||
return noop;
|
||||
},
|
||||
[setPopperRoot, emojiResults]
|
||||
);
|
||||
|
||||
const onFocus = React.useCallback(
|
||||
() => {
|
||||
focusRef.current = true;
|
||||
},
|
||||
[focusRef]
|
||||
);
|
||||
|
||||
const onBlur = React.useCallback(
|
||||
() => {
|
||||
focusRef.current = false;
|
||||
},
|
||||
[focusRef]
|
||||
);
|
||||
|
||||
// Manage focus
|
||||
// Chromium places the editor caret at the beginning of contenteditable divs on focus
|
||||
// Here, we force the last known selection on focusin (doing this with onFocus wasn't behaving properly)
|
||||
// This needs to be done in an effect because React doesn't support focus{In,Out}
|
||||
// https://github.com/facebook/react/issues/6410
|
||||
React.useLayoutEffect(
|
||||
() => {
|
||||
const { current: rootEl } = rootElRef;
|
||||
|
||||
if (rootEl) {
|
||||
const onFocusIn = () => {
|
||||
const { current: oldState } = editorStateRef;
|
||||
// Force selection to be old selection
|
||||
setAndTrackEditorState(
|
||||
EditorState.forceSelection(oldState, oldState.getSelection())
|
||||
);
|
||||
};
|
||||
|
||||
rootEl.addEventListener('focusin', onFocusIn);
|
||||
|
||||
return () => {
|
||||
rootEl.removeEventListener('focusin', onFocusIn);
|
||||
};
|
||||
}
|
||||
|
||||
return noop;
|
||||
},
|
||||
[editorStateRef, rootElRef, setAndTrackEditorState]
|
||||
);
|
||||
|
||||
if (inputApi) {
|
||||
inputApi.current = {
|
||||
reset: resetEditorState,
|
||||
submit,
|
||||
insertEmoji,
|
||||
resetEmojiResults,
|
||||
};
|
||||
}
|
||||
|
||||
return (
|
||||
<Manager>
|
||||
<Reference>
|
||||
{({ ref: popperRef }) => (
|
||||
<Measure bounds={true} onResize={handleEditorSizeChange}>
|
||||
{({ measureRef }) => (
|
||||
<div
|
||||
className="module-composition-input__input"
|
||||
ref={combineRefs(popperRef, measureRef, rootElRef)}
|
||||
>
|
||||
<div className="module-composition-input__input__scroller">
|
||||
<Editor
|
||||
ref={editorRef}
|
||||
editorState={editorState}
|
||||
onChange={handleEditorStateChange}
|
||||
placeholder={i18n('sendMessage')}
|
||||
onUpArrow={handleEditorArrowKey}
|
||||
onDownArrow={handleEditorArrowKey}
|
||||
onEscape={handleEscapeKey}
|
||||
onTab={onTab}
|
||||
handleKeyCommand={handleEditorCommand}
|
||||
keyBindingFn={editorKeybindingFn}
|
||||
spellCheck={true}
|
||||
stripPastedStyles={true}
|
||||
readOnly={disabled}
|
||||
onFocus={onFocus}
|
||||
onBlur={onBlur}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Measure>
|
||||
)}
|
||||
</Reference>
|
||||
{emojiResults.length > 0 && popperRoot
|
||||
? createPortal(
|
||||
<Popper placement="top" key={searchText}>
|
||||
{({ ref, style }) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className="module-composition-input__emoji-suggestions"
|
||||
style={{
|
||||
...style,
|
||||
width: editorWidth,
|
||||
}}
|
||||
role="listbox"
|
||||
aria-expanded={true}
|
||||
aria-activedescendant={`emoji-result--${
|
||||
emojiResults[emojiResultsIndex].short_name
|
||||
}`}
|
||||
>
|
||||
{emojiResults.map((emoji, index) => (
|
||||
<button
|
||||
key={emoji.short_name}
|
||||
id={`emoji-result--${emoji.short_name}`}
|
||||
role="option button"
|
||||
aria-selected={emojiResultsIndex === index}
|
||||
onMouseDown={() => {
|
||||
insertEmoji(
|
||||
{ shortName: emoji.short_name, skinTone },
|
||||
true
|
||||
);
|
||||
onPickEmoji({ shortName: emoji.short_name });
|
||||
}}
|
||||
className={classNames(
|
||||
'module-composition-input__emoji-suggestions__row',
|
||||
emojiResultsIndex === index
|
||||
? 'module-composition-input__emoji-suggestions__row--selected'
|
||||
: null
|
||||
)}
|
||||
>
|
||||
<Emoji
|
||||
shortName={emoji.short_name}
|
||||
size={16}
|
||||
skinTone={skinTone}
|
||||
/>
|
||||
<div className="module-composition-input__emoji-suggestions__row__short-name">
|
||||
:{emoji.short_name}:
|
||||
</div>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</Popper>,
|
||||
popperRoot
|
||||
)
|
||||
: null}
|
||||
</Manager>
|
||||
);
|
||||
};
|
|
@ -60,7 +60,7 @@ export class ContactListItem extends React.Component<Props> {
|
|||
const profileElement =
|
||||
!isMe && profileName && !name ? (
|
||||
<span className="module-contact-list-item__text__profile-name">
|
||||
~<Emojify text={profileName} i18n={i18n} />
|
||||
~<Emojify text={profileName} />
|
||||
</span>
|
||||
) : null;
|
||||
|
||||
|
@ -79,7 +79,7 @@ export class ContactListItem extends React.Component<Props> {
|
|||
{this.renderAvatar()}
|
||||
<div className="module-contact-list-item__text">
|
||||
<div className="module-contact-list-item__text__name">
|
||||
<Emojify text={displayName} i18n={i18n} /> {profileElement}
|
||||
<Emojify text={displayName} /> {profileElement}
|
||||
</div>
|
||||
<div className="module-contact-list-item__text__additional-data">
|
||||
{showVerified ? (
|
||||
|
|
|
@ -111,7 +111,6 @@ export class ConversationListItem extends React.PureComponent<Props> {
|
|||
phoneNumber={phoneNumber}
|
||||
name={name}
|
||||
profileName={profileName}
|
||||
i18n={i18n}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -18,7 +18,6 @@ const renderNewLines: RenderTextCallbackType = ({ text, key }) => (
|
|||
);
|
||||
|
||||
const renderEmoji = ({
|
||||
i18n,
|
||||
text,
|
||||
key,
|
||||
sizeClass,
|
||||
|
@ -31,7 +30,6 @@ const renderEmoji = ({
|
|||
renderNonEmoji: RenderTextCallbackType;
|
||||
}) => (
|
||||
<Emojify
|
||||
i18n={i18n}
|
||||
key={key}
|
||||
text={text}
|
||||
sizeClass={sizeClass}
|
||||
|
|
|
@ -66,7 +66,6 @@ export class MessageSearchResult extends React.PureComponent<Props> {
|
|||
phoneNumber={from.phoneNumber}
|
||||
name={from.name}
|
||||
profileName={from.profileName}
|
||||
i18n={i18n}
|
||||
module="module-message-search-result__header__name"
|
||||
/>
|
||||
);
|
||||
|
@ -85,7 +84,6 @@ export class MessageSearchResult extends React.PureComponent<Props> {
|
|||
phoneNumber={to.phoneNumber}
|
||||
name={to.name}
|
||||
profileName={to.profileName}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
```jsx
|
||||
<ContactName
|
||||
i18n={util.i18n}
|
||||
name="Someone 🔥 Somewhere"
|
||||
phoneNumber="(202) 555-0011"
|
||||
profileName="🔥Flames🔥"
|
||||
|
@ -12,15 +11,11 @@
|
|||
#### Number and profile, no name
|
||||
|
||||
```jsx
|
||||
<ContactName
|
||||
i18n={util.i18n}
|
||||
phoneNumber="(202) 555-0011"
|
||||
profileName="🔥Flames🔥"
|
||||
/>
|
||||
<ContactName phoneNumber="(202) 555-0011" profileName="🔥Flames🔥" />
|
||||
```
|
||||
|
||||
#### No name, no profile
|
||||
|
||||
```jsx
|
||||
<ContactName i18n={util.i18n} phoneNumber="(202) 555-0011" />
|
||||
<ContactName phoneNumber="(202) 555-0011" />
|
||||
```
|
||||
|
|
|
@ -2,32 +2,29 @@ import React from 'react';
|
|||
|
||||
import { Emojify } from './Emojify';
|
||||
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
interface Props {
|
||||
phoneNumber: string;
|
||||
name?: string;
|
||||
profileName?: string;
|
||||
i18n: LocalizerType;
|
||||
module?: string;
|
||||
}
|
||||
|
||||
export class ContactName extends React.Component<Props> {
|
||||
public render() {
|
||||
const { phoneNumber, name, profileName, i18n, module } = this.props;
|
||||
const { phoneNumber, name, profileName, module } = this.props;
|
||||
const prefix = module ? module : 'module-contact-name';
|
||||
|
||||
const title = name ? name : phoneNumber;
|
||||
const shouldShowProfile = Boolean(profileName && !name);
|
||||
const profileElement = shouldShowProfile ? (
|
||||
<span className={`${prefix}__profile-name`}>
|
||||
~<Emojify text={profileName || ''} i18n={i18n} />
|
||||
~<Emojify text={profileName || ''} />
|
||||
</span>
|
||||
) : null;
|
||||
|
||||
return (
|
||||
<span className={prefix} dir="auto">
|
||||
<Emojify text={title} i18n={i18n} />
|
||||
<Emojify text={title} />
|
||||
{shouldShowProfile ? ' ' : null}
|
||||
{profileElement}
|
||||
</span>
|
||||
|
|
|
@ -101,12 +101,12 @@ export class ConversationHeader extends React.Component<Props> {
|
|||
|
||||
return (
|
||||
<div className="module-conversation-header__title">
|
||||
{name ? <Emojify text={name} i18n={i18n} /> : null}
|
||||
{name ? <Emojify text={name} /> : null}
|
||||
{name && phoneNumber ? ' · ' : null}
|
||||
{phoneNumber ? phoneNumber : null}{' '}
|
||||
{profileName && !name ? (
|
||||
<span className="module-conversation-header__title__profile-name">
|
||||
~<Emojify text={profileName} i18n={i18n} />
|
||||
~<Emojify text={profileName} />
|
||||
</span>
|
||||
) : null}
|
||||
{isVerified ? ' · ' : null}
|
||||
|
|
|
@ -1,53 +1,53 @@
|
|||
### All emoji
|
||||
|
||||
```jsx
|
||||
<Emojify text="🔥🔥🔥" i18n={util.i18n} />
|
||||
<Emojify text="🔥🔥🔥" />
|
||||
```
|
||||
|
||||
### With skin color modifier
|
||||
|
||||
```jsx
|
||||
<Emojify text="👍🏾" i18n={util.i18n} />
|
||||
<Emojify text="👍🏾" />
|
||||
```
|
||||
|
||||
### With `sizeClass` provided
|
||||
|
||||
```jsx
|
||||
<Emojify text="🔥" sizeClass="jumbo" i18n={util.i18n} />
|
||||
<Emojify text="🔥" sizeClass="jumbo" />
|
||||
```
|
||||
|
||||
```jsx
|
||||
<Emojify text="🔥" sizeClass="large" i18n={util.i18n} />
|
||||
<Emojify text="🔥" sizeClass="large" />
|
||||
```
|
||||
|
||||
```jsx
|
||||
<Emojify text="🔥" sizeClass="medium" i18n={util.i18n} />
|
||||
<Emojify text="🔥" sizeClass="medium" />
|
||||
```
|
||||
|
||||
```jsx
|
||||
<Emojify text="🔥" sizeClass="small" i18n={util.i18n} />
|
||||
<Emojify text="🔥" sizeClass="small" />
|
||||
```
|
||||
|
||||
```jsx
|
||||
<Emojify text="🔥" sizeClass="" i18n={util.i18n} />
|
||||
<Emojify text="🔥" sizeClass="" />
|
||||
```
|
||||
|
||||
### Starting and ending with emoji
|
||||
|
||||
```jsx
|
||||
<Emojify text="🔥in between🔥" i18n={util.i18n} />
|
||||
<Emojify text="🔥in between🔥" />
|
||||
```
|
||||
|
||||
### With emoji in the middle
|
||||
|
||||
```jsx
|
||||
<Emojify text="Before 🔥🔥 after" i18n={util.i18n} />
|
||||
<Emojify text="Before 🔥🔥 after" />
|
||||
```
|
||||
|
||||
### No emoji
|
||||
|
||||
```jsx
|
||||
<Emojify text="This is the text" i18n={util.i18n} />
|
||||
<Emojify text="This is the text" />
|
||||
```
|
||||
|
||||
### Providing custom non-link render function
|
||||
|
@ -56,9 +56,5 @@
|
|||
const renderNonEmoji = ({ text, key }) => (
|
||||
<span key={key}>This is my custom content</span>
|
||||
);
|
||||
<Emojify
|
||||
text="Before 🔥🔥 after"
|
||||
renderNonEmoji={renderNonEmoji}
|
||||
i18n={util.i18n}
|
||||
/>;
|
||||
<Emojify text="Before 🔥🔥 after" renderNonEmoji={renderNonEmoji} />;
|
||||
```
|
||||
|
|
|
@ -7,23 +7,20 @@ import {
|
|||
findImage,
|
||||
getRegex,
|
||||
getReplacementData,
|
||||
getTitle,
|
||||
SizeClassType,
|
||||
} from '../../util/emoji';
|
||||
|
||||
import { LocalizerType, RenderTextCallbackType } from '../../types/Util';
|
||||
import { RenderTextCallbackType } from '../../types/Util';
|
||||
|
||||
// Some of this logic taken from emoji-js/replacement
|
||||
function getImageTag({
|
||||
match,
|
||||
sizeClass,
|
||||
key,
|
||||
i18n,
|
||||
}: {
|
||||
match: any;
|
||||
sizeClass?: SizeClassType;
|
||||
key: string | number;
|
||||
i18n: LocalizerType;
|
||||
}) {
|
||||
const result = getReplacementData(match[0], match[1], match[2]);
|
||||
|
||||
|
@ -32,7 +29,6 @@ function getImageTag({
|
|||
}
|
||||
|
||||
const img = findImage(result.value, result.variation);
|
||||
const title = getTitle(result.value);
|
||||
|
||||
if (
|
||||
!img.path ||
|
||||
|
@ -46,12 +42,10 @@ function getImageTag({
|
|||
<img
|
||||
key={key}
|
||||
src={img.path}
|
||||
// We can't use alt or it will be what is captured when a user copies message
|
||||
// contents ("Emoji of ':1'"). Instead, we want the title to be copied (':+1:').
|
||||
aria-label={i18n('emojiAlt', [title || ''])}
|
||||
aria-label={match[0]}
|
||||
className={classNames('emoji', sizeClass)}
|
||||
data-codepoints={img.full_idx}
|
||||
title={`:${title}:`}
|
||||
title={match[0]}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -62,7 +56,6 @@ interface Props {
|
|||
sizeClass?: SizeClassType;
|
||||
/** Allows you to customize now non-newlines are rendered. Simplest is just a <span>. */
|
||||
renderNonEmoji?: RenderTextCallbackType;
|
||||
i18n: LocalizerType;
|
||||
}
|
||||
|
||||
export class Emojify extends React.Component<Props> {
|
||||
|
@ -71,7 +64,7 @@ export class Emojify extends React.Component<Props> {
|
|||
};
|
||||
|
||||
public render() {
|
||||
const { text, sizeClass, renderNonEmoji, i18n } = this.props;
|
||||
const { text, sizeClass, renderNonEmoji } = this.props;
|
||||
const results: Array<any> = [];
|
||||
const regex = getRegex();
|
||||
|
||||
|
@ -95,7 +88,7 @@ export class Emojify extends React.Component<Props> {
|
|||
results.push(renderNonEmoji({ text: textWithNoEmoji, key: count++ }));
|
||||
}
|
||||
|
||||
results.push(getImageTag({ match, sizeClass, key: count++, i18n }));
|
||||
results.push(getImageTag({ match, sizeClass, key: count++ }));
|
||||
|
||||
last = regex.lastIndex;
|
||||
match = regex.exec(text);
|
||||
|
|
|
@ -45,7 +45,6 @@ export class GroupNotification extends React.Component<Props> {
|
|||
className="module-group-notification__contact"
|
||||
>
|
||||
<ContactName
|
||||
i18n={i18n}
|
||||
phoneNumber={contact.phoneNumber}
|
||||
profileName={contact.profileName}
|
||||
name={contact.name}
|
||||
|
|
|
@ -330,7 +330,6 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
collapseMetadata,
|
||||
conversationType,
|
||||
direction,
|
||||
i18n,
|
||||
isSticker,
|
||||
isTapToView,
|
||||
isTapToViewExpired,
|
||||
|
@ -361,7 +360,6 @@ export class Message extends React.PureComponent<Props, State> {
|
|||
name={authorName}
|
||||
profileName={authorProfileName}
|
||||
module={moduleName}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -23,7 +23,6 @@ const renderNewLines: RenderTextCallbackType = ({
|
|||
}) => <AddNewLines key={key} text={textWithNewLines} />;
|
||||
|
||||
const renderEmoji = ({
|
||||
i18n,
|
||||
text,
|
||||
key,
|
||||
sizeClass,
|
||||
|
@ -36,7 +35,6 @@ const renderEmoji = ({
|
|||
renderNonEmoji: RenderTextCallbackType;
|
||||
}) => (
|
||||
<Emojify
|
||||
i18n={i18n}
|
||||
key={key}
|
||||
text={text}
|
||||
sizeClass={sizeClass}
|
||||
|
|
|
@ -111,7 +111,6 @@ export class MessageDetail extends React.Component<Props> {
|
|||
phoneNumber={contact.phoneNumber}
|
||||
name={contact.name}
|
||||
profileName={contact.profileName}
|
||||
i18n={i18n}
|
||||
/>
|
||||
</div>
|
||||
{errors.map((error, index) => (
|
||||
|
|
|
@ -303,7 +303,6 @@ export class Quote extends React.Component<Props, State> {
|
|||
phoneNumber={authorPhoneNumber}
|
||||
name={authorName}
|
||||
profileName={authorProfileName}
|
||||
i18n={i18n}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -46,7 +46,6 @@ export class SafetyNumberNotification extends React.Component<Props> {
|
|||
className="module-safety-number-notification__contact"
|
||||
>
|
||||
<ContactName
|
||||
i18n={i18n}
|
||||
name={contact.name}
|
||||
profileName={contact.profileName}
|
||||
phoneNumber={contact.phoneNumber}
|
||||
|
|
|
@ -45,7 +45,6 @@ export class TimerNotification extends React.Component<Props> {
|
|||
id={changeKey}
|
||||
components={[
|
||||
<ContactName
|
||||
i18n={i18n}
|
||||
key="external-1"
|
||||
phoneNumber={phoneNumber}
|
||||
profileName={profileName}
|
||||
|
|
|
@ -60,7 +60,6 @@ export class UnsupportedMessage extends React.Component<Props> {
|
|||
className="module-unsupported-message__contact"
|
||||
>
|
||||
<ContactName
|
||||
i18n={i18n}
|
||||
name={contact.name}
|
||||
profileName={contact.profileName}
|
||||
phoneNumber={contact.phoneNumber}
|
||||
|
|
|
@ -52,7 +52,6 @@ export class VerificationNotification extends React.Component<Props> {
|
|||
id={id}
|
||||
components={[
|
||||
<ContactName
|
||||
i18n={i18n}
|
||||
key="external-1"
|
||||
name={contact.name}
|
||||
profileName={contact.profileName}
|
||||
|
|
|
@ -6,7 +6,8 @@ export type OwnProps = {
|
|||
inline?: boolean;
|
||||
shortName: string;
|
||||
skinTone?: SkinToneKey | number;
|
||||
size?: 16 | 20 | 28 | 32 | 64 | 66;
|
||||
size?: 16 | 18 | 20 | 28 | 32 | 64 | 66;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
export type Props = OwnProps &
|
||||
|
@ -15,28 +16,45 @@ export type Props = OwnProps &
|
|||
export const Emoji = React.memo(
|
||||
React.forwardRef<HTMLDivElement, Props>(
|
||||
(
|
||||
{ style = {}, size = 28, shortName, skinTone, inline, className }: Props,
|
||||
{
|
||||
style = {},
|
||||
size = 28,
|
||||
shortName,
|
||||
skinTone,
|
||||
inline,
|
||||
className,
|
||||
children,
|
||||
}: Props,
|
||||
ref
|
||||
) => {
|
||||
const image = getImagePath(shortName, skinTone);
|
||||
const backgroundStyle = inline
|
||||
? { backgroundImage: `url('${image}')` }
|
||||
: {};
|
||||
|
||||
return (
|
||||
<div
|
||||
<span
|
||||
ref={ref}
|
||||
className={classNames(
|
||||
'module-emoji',
|
||||
`module-emoji--${size}px`,
|
||||
inline ? 'module-emoji--inline' : null,
|
||||
inline ? `module-emoji--${size}px--inline` : null,
|
||||
className
|
||||
)}
|
||||
style={style}
|
||||
style={{ ...style, ...backgroundStyle }}
|
||||
>
|
||||
<img
|
||||
className={`module-emoji__image--${size}px`}
|
||||
src={image}
|
||||
alt={shortName}
|
||||
/>
|
||||
</div>
|
||||
{inline ? (
|
||||
// When using this component as a draft.js decorator it is very
|
||||
// important that these children are the only elements to render
|
||||
children
|
||||
) : (
|
||||
<img
|
||||
className={`module-emoji__image--${size}px`}
|
||||
src={image}
|
||||
alt={shortName}
|
||||
/>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
)
|
||||
|
|
|
@ -3,9 +3,15 @@ import classNames from 'classnames';
|
|||
import { noop } from 'lodash';
|
||||
import { Manager, Popper, Reference } from 'react-popper';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { EmojiPicker, Props as EmojiPickerProps } from './EmojiPicker';
|
||||
import {
|
||||
EmojiPickDataType,
|
||||
EmojiPicker,
|
||||
Props as EmojiPickerProps,
|
||||
} from './EmojiPicker';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
export type EmojiPickDataType = EmojiPickDataType;
|
||||
|
||||
export type OwnProps = {
|
||||
readonly i18n: LocalizerType;
|
||||
};
|
||||
|
@ -14,22 +20,22 @@ export type Props = OwnProps &
|
|||
Pick<
|
||||
EmojiPickerProps,
|
||||
| 'onClose'
|
||||
| 'onForceSend'
|
||||
| 'doSend'
|
||||
| 'onPickEmoji'
|
||||
| 'skinTone'
|
||||
| 'onSetSkinTone'
|
||||
| 'recentEmojis'
|
||||
| 'skinTone'
|
||||
>;
|
||||
|
||||
export const EmojiButton = React.memo(
|
||||
({
|
||||
i18n,
|
||||
onClose,
|
||||
onForceSend,
|
||||
doSend,
|
||||
onPickEmoji,
|
||||
skinTone,
|
||||
onSetSkinTone,
|
||||
recentEmojis,
|
||||
onClose,
|
||||
}: Props) => {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [popperRoot, setPopperRoot] = React.useState<HTMLElement | null>(
|
||||
|
@ -49,8 +55,8 @@ export const EmojiButton = React.memo(
|
|||
|
||||
const handleClose = React.useCallback(
|
||||
() => {
|
||||
onClose();
|
||||
setOpen(false);
|
||||
onClose();
|
||||
},
|
||||
[setOpen, onClose]
|
||||
);
|
||||
|
@ -65,6 +71,7 @@ export const EmojiButton = React.memo(
|
|||
const handleOutsideClick = ({ target }: MouseEvent) => {
|
||||
if (!root.contains(target as Node)) {
|
||||
setOpen(false);
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
document.addEventListener('click', handleOutsideClick);
|
||||
|
@ -104,7 +111,7 @@ export const EmojiButton = React.memo(
|
|||
i18n={i18n}
|
||||
style={style}
|
||||
onPickEmoji={onPickEmoji}
|
||||
onForceSend={onForceSend}
|
||||
doSend={doSend}
|
||||
onClose={handleClose}
|
||||
skinTone={skinTone}
|
||||
onSetSkinTone={onSetSkinTone}
|
||||
|
|
|
@ -19,10 +19,12 @@ import { Emoji } from './Emoji';
|
|||
import { dataByCategory, search } from './lib';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
export type EmojiPickDataType = { skinTone?: number; shortName: string };
|
||||
|
||||
export type OwnProps = {
|
||||
readonly i18n: LocalizerType;
|
||||
readonly onPickEmoji: (o: { skinTone: number; shortName: string }) => unknown;
|
||||
readonly onForceSend: () => unknown;
|
||||
readonly onPickEmoji: (o: EmojiPickDataType) => unknown;
|
||||
readonly doSend: () => unknown;
|
||||
readonly skinTone: number;
|
||||
readonly onSetSkinTone: (tone: number) => unknown;
|
||||
readonly recentEmojis: Array<string>;
|
||||
|
@ -57,7 +59,7 @@ export const EmojiPicker = React.memo(
|
|||
(
|
||||
{
|
||||
i18n,
|
||||
onForceSend,
|
||||
doSend,
|
||||
onPickEmoji,
|
||||
skinTone = 0,
|
||||
onSetSkinTone,
|
||||
|
@ -123,7 +125,7 @@ export const EmojiPicker = React.memo(
|
|||
if ('key' in e) {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
onForceSend();
|
||||
doSend();
|
||||
}
|
||||
} else {
|
||||
const { shortName } = e.currentTarget.dataset;
|
||||
|
@ -132,7 +134,7 @@ export const EmojiPicker = React.memo(
|
|||
}
|
||||
}
|
||||
},
|
||||
[onClose, onForceSend, onPickEmoji, selectedTone]
|
||||
[doSend, onPickEmoji, selectedTone]
|
||||
);
|
||||
|
||||
// Handle escape key
|
||||
|
|
|
@ -9,6 +9,7 @@ import {
|
|||
map,
|
||||
mapValues,
|
||||
sortBy,
|
||||
take,
|
||||
} from 'lodash';
|
||||
import Fuse from 'fuse.js';
|
||||
import PQueue from 'p-queue';
|
||||
|
@ -194,8 +195,14 @@ const fuse = new Fuse(data, {
|
|||
keys: ['name', 'short_name', 'short_names'],
|
||||
});
|
||||
|
||||
export function search(query: string) {
|
||||
return fuse.search(query.substr(0, 32));
|
||||
export function search(query: string, count: number = 0) {
|
||||
const results = fuse.search(query.substr(0, 32));
|
||||
|
||||
if (count) {
|
||||
return take(results, count);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
const shortNames = new Set([
|
||||
|
|
|
@ -4,6 +4,7 @@ import { noop } from 'lodash';
|
|||
import { Manager, Popper, Reference } from 'react-popper';
|
||||
import { createPortal } from 'react-dom';
|
||||
import { StickerPicker } from './StickerPicker';
|
||||
import { countStickers } from './lib';
|
||||
import { StickerPackType, StickerType } from '../../state/ducks/stickers';
|
||||
import { LocalizerType } from '../../types/Util';
|
||||
|
||||
|
@ -150,12 +151,14 @@ export const StickerButton = React.memo(
|
|||
[installedPack, clearInstalledStickerPack]
|
||||
);
|
||||
|
||||
const totalPacks =
|
||||
knownPacks.length +
|
||||
blessedPacks.length +
|
||||
installedPacks.length +
|
||||
receivedPacks.length;
|
||||
if (totalPacks === 0) {
|
||||
if (
|
||||
countStickers({
|
||||
knownPacks,
|
||||
blessedPacks,
|
||||
installedPacks,
|
||||
receivedPacks,
|
||||
}) === 0
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
17
ts/components/stickers/lib.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { StickerPackType } from '../../state/ducks/stickers';
|
||||
|
||||
// This function exists to force stickers to be counted consistently wherever
|
||||
// they are counted (TypeScript ensures that all data is named and provided)
|
||||
export function countStickers(o: {
|
||||
knownPacks: ReadonlyArray<StickerPackType>;
|
||||
blessedPacks: ReadonlyArray<StickerPackType>;
|
||||
installedPacks: ReadonlyArray<StickerPackType>;
|
||||
receivedPacks: ReadonlyArray<StickerPackType>;
|
||||
}) {
|
||||
return (
|
||||
o.knownPacks.length +
|
||||
o.blessedPacks.length +
|
||||
o.installedPacks.length +
|
||||
o.receivedPacks.length
|
||||
);
|
||||
}
|
|
@ -1,4 +1,5 @@
|
|||
import { take, uniq } from 'lodash';
|
||||
import { EmojiPickDataType } from '../../components/emoji/EmojiPicker';
|
||||
import { updateEmojiUsage } from '../../../js/modules/data';
|
||||
|
||||
// State
|
||||
|
@ -27,7 +28,7 @@ export const actions = {
|
|||
useEmoji,
|
||||
};
|
||||
|
||||
function useEmoji(shortName: string): UseEmojiAction {
|
||||
function useEmoji({ shortName }: EmojiPickDataType): UseEmojiAction {
|
||||
return {
|
||||
type: 'emojis/USE_EMOJI',
|
||||
payload: doUseEmoji(shortName),
|
||||
|
|
|
@ -3,14 +3,14 @@ import { Provider } from 'react-redux';
|
|||
|
||||
import { Store } from 'redux';
|
||||
|
||||
import { SmartEmojiButton } from '../smart/EmojiButton';
|
||||
import { SmartCompositionArea } from '../smart/CompositionArea';
|
||||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
const FilteredEmojiButton = SmartEmojiButton as any;
|
||||
const FilteredCompositionArea = SmartCompositionArea as any;
|
||||
|
||||
export const createEmojiButton = (store: Store, props: Object) => (
|
||||
export const createCompositionArea = (store: Store, props: Object) => (
|
||||
<Provider store={store}>
|
||||
<FilteredEmojiButton {...props} />
|
||||
<FilteredCompositionArea {...props} />
|
||||
</Provider>
|
||||
);
|
|
@ -1,16 +0,0 @@
|
|||
import React from 'react';
|
||||
import { Provider } from 'react-redux';
|
||||
|
||||
import { Store } from 'redux';
|
||||
|
||||
import { SmartStickerButton } from '../smart/StickerButton';
|
||||
|
||||
// Workaround: A react component's required properties are filtering up through connect()
|
||||
// https://github.com/DefinitelyTyped/DefinitelyTyped/issues/31363
|
||||
const FilteredStickerButton = SmartStickerButton as any;
|
||||
|
||||
export const createStickerButton = (store: Store, props: Object) => (
|
||||
<Provider store={store}>
|
||||
<FilteredStickerButton {...props} />
|
||||
</Provider>
|
||||
);
|
|
@ -1,9 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { get } from 'lodash';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import { StickerButton } from '../../components/stickers/StickerButton';
|
||||
import { CompositionArea } from '../../components/CompositionArea';
|
||||
import { StateType } from '../reducer';
|
||||
|
||||
import { isShortName } from '../../components/emoji/lib';
|
||||
import { getIntl } from '../selectors/user';
|
||||
import {
|
||||
getBlessedStickerPacks,
|
||||
|
@ -14,6 +16,11 @@ import {
|
|||
getRecentStickers,
|
||||
} from '../selectors/stickers';
|
||||
|
||||
const selectRecentEmojis = createSelector(
|
||||
({ emojis }: StateType) => emojis.recents,
|
||||
recents => recents.filter(isShortName)
|
||||
);
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
const receivedPacks = getReceivedStickerPacks(state);
|
||||
const installedPacks = getInstalledStickerPacks(state);
|
||||
|
@ -31,7 +38,15 @@ const mapStateToProps = (state: StateType) => {
|
|||
get(state.items, ['showStickerPickerHint'], false) &&
|
||||
receivedPacks.length > 0;
|
||||
|
||||
const recentEmojis = selectRecentEmojis(state);
|
||||
|
||||
return {
|
||||
// Base
|
||||
i18n: getIntl(state),
|
||||
// Emojis
|
||||
recentEmojis,
|
||||
skinTone: get(state, ['items', 'skinTone'], 0),
|
||||
// Stickers
|
||||
receivedPacks,
|
||||
installedPack,
|
||||
blessedPacks,
|
||||
|
@ -40,16 +55,19 @@ const mapStateToProps = (state: StateType) => {
|
|||
recentStickers,
|
||||
showIntroduction,
|
||||
showPickerHint,
|
||||
i18n: getIntl(state),
|
||||
};
|
||||
};
|
||||
|
||||
const smart = connect(mapStateToProps, {
|
||||
const dispatchPropsMap = {
|
||||
...mapDispatchToProps,
|
||||
onSetSkinTone: (tone: number) => mapDispatchToProps.putItem('skinTone', tone),
|
||||
clearShowIntroduction: () =>
|
||||
mapDispatchToProps.removeItem('showStickersIntroduction'),
|
||||
clearShowPickerHint: () =>
|
||||
mapDispatchToProps.removeItem('showStickerPickerHint'),
|
||||
});
|
||||
onPickEmoji: mapDispatchToProps.useEmoji,
|
||||
};
|
||||
|
||||
export const SmartStickerButton = smart(StickerButton);
|
||||
const smart = connect(mapStateToProps, dispatchPropsMap);
|
||||
|
||||
export const SmartCompositionArea = smart(CompositionArea);
|
|
@ -1,58 +0,0 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { get } from 'lodash';
|
||||
import { mapDispatchToProps } from '../actions';
|
||||
import { EmojiButton, Props } from '../../components/emoji/EmojiButton';
|
||||
import { StateType } from '../reducer';
|
||||
|
||||
import { isShortName } from '../../components/emoji/lib';
|
||||
import { getIntl } from '../selectors/user';
|
||||
|
||||
const selectRecentEmojis = createSelector(
|
||||
({ emojis }: StateType) => emojis.recents,
|
||||
recents => recents.filter(isShortName)
|
||||
);
|
||||
|
||||
const mapStateToProps = (state: StateType) => {
|
||||
return {
|
||||
i18n: getIntl(state),
|
||||
recentEmojis: selectRecentEmojis(state),
|
||||
skinTone: get(state, ['items', 'skinTone'], 0),
|
||||
};
|
||||
};
|
||||
|
||||
const dispatchPropsMap = {
|
||||
...mapDispatchToProps,
|
||||
onSetSkinTone: (tone: number) => mapDispatchToProps.putItem('skinTone', tone),
|
||||
};
|
||||
|
||||
type OnPickEmojiType = Props['onPickEmoji'];
|
||||
type UseEmojiType = typeof mapDispatchToProps.useEmoji;
|
||||
|
||||
export type OwnProps = {
|
||||
onPickEmoji: OnPickEmojiType;
|
||||
};
|
||||
|
||||
const selectOnPickEmoji = createSelector(
|
||||
(onPickEmoji: OnPickEmojiType) => onPickEmoji,
|
||||
(_onPickEmoji: OnPickEmojiType, useEmoji: UseEmojiType) => useEmoji,
|
||||
(onPickEmoji, useEmoji): OnPickEmojiType => e => {
|
||||
onPickEmoji(e);
|
||||
useEmoji(e.shortName);
|
||||
}
|
||||
);
|
||||
|
||||
const mergeProps = (
|
||||
stateProps: ReturnType<typeof mapStateToProps>,
|
||||
dispatchProps: typeof dispatchPropsMap,
|
||||
ownProps: OwnProps
|
||||
) => ({
|
||||
...ownProps,
|
||||
...stateProps,
|
||||
...dispatchProps,
|
||||
onPickEmoji: selectOnPickEmoji(ownProps.onPickEmoji, dispatchProps.useEmoji),
|
||||
});
|
||||
|
||||
const smart = connect(mapStateToProps, dispatchPropsMap, mergeProps);
|
||||
|
||||
export const SmartEmojiButton = smart(EmojiButton);
|
|
@ -477,7 +477,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " let $el = this.$(`#${id}`);",
|
||||
"lineNumber": 33,
|
||||
"lineNumber": 34,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2018-09-19T21:59:32.770Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
|
@ -486,7 +486,7 @@
|
|||
"rule": "jQuery-prependTo(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " $el.prependTo(this.el);",
|
||||
"lineNumber": 42,
|
||||
"lineNumber": 43,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2018-09-19T18:13:29.628Z",
|
||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||
|
@ -495,7 +495,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('.message').text(message);",
|
||||
"lineNumber": 56,
|
||||
"lineNumber": 61,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2018-09-19T21:59:32.770Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
|
@ -504,7 +504,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " el: this.$('.conversation-stack'),",
|
||||
"lineNumber": 73,
|
||||
"lineNumber": 78,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2018-09-19T21:59:32.770Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
|
@ -513,7 +513,7 @@
|
|||
"rule": "jQuery-prependTo(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.appLoadingScreen.$el.prependTo(this.el);",
|
||||
"lineNumber": 80,
|
||||
"lineNumber": 85,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2018-09-19T18:13:29.628Z",
|
||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||
|
@ -522,7 +522,7 @@
|
|||
"rule": "jQuery-append(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " .append(this.networkStatusView.render().el);",
|
||||
"lineNumber": 95,
|
||||
"lineNumber": 100,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2018-09-19T18:13:29.628Z",
|
||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||
|
@ -531,7 +531,7 @@
|
|||
"rule": "jQuery-prependTo(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " banner.$el.prependTo(this.$el);",
|
||||
"lineNumber": 99,
|
||||
"lineNumber": 104,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2018-09-19T18:13:29.628Z",
|
||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||
|
@ -540,7 +540,7 @@
|
|||
"rule": "jQuery-appendTo(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " toast.$el.appendTo(this.$el);",
|
||||
"lineNumber": 105,
|
||||
"lineNumber": 110,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-05-10T00:25:51.515Z",
|
||||
"reasonDetail": "Interacting with already-existing DOM nodes"
|
||||
|
@ -549,7 +549,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
|
||||
"lineNumber": 125,
|
||||
"lineNumber": 130,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-03-08T23:49:08.796Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
|
@ -558,7 +558,7 @@
|
|||
"rule": "jQuery-append(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('.left-pane-placeholder').append(this.leftPaneView.el);",
|
||||
"lineNumber": 125,
|
||||
"lineNumber": 130,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-03-08T23:49:08.796Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
|
@ -567,7 +567,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " if (e && this.$(e.target).closest('.placeholder').length) {",
|
||||
"lineNumber": 166,
|
||||
"lineNumber": 171,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-03-08T23:49:08.796Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
|
@ -576,7 +576,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('#header, .gutter').addClass('inactive');",
|
||||
"lineNumber": 170,
|
||||
"lineNumber": 175,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-03-08T23:49:08.796Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
|
@ -585,7 +585,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('.conversation-stack').addClass('inactive');",
|
||||
"lineNumber": 174,
|
||||
"lineNumber": 179,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-03-08T23:49:08.796Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
|
@ -594,7 +594,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('.conversation:first .menu').trigger('close');",
|
||||
"lineNumber": 176,
|
||||
"lineNumber": 181,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-03-08T23:49:08.796Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
|
@ -603,7 +603,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " if (e && this.$(e.target).closest('.capture-audio').length > 0) {",
|
||||
"lineNumber": 196,
|
||||
"lineNumber": 201,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-03-08T23:49:08.796Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
|
@ -612,7 +612,7 @@
|
|||
"rule": "jQuery-$(",
|
||||
"path": "js/views/inbox_view.js",
|
||||
"line": " this.$('.conversation:first .recorder').trigger('close');",
|
||||
"lineNumber": 199,
|
||||
"lineNumber": 204,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-03-08T23:49:08.796Z",
|
||||
"reasonDetail": "Protected from arbitrary input"
|
||||
|
@ -2642,77 +2642,6 @@
|
|||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-04-26T19:26:59.689Z"
|
||||
},
|
||||
{
|
||||
"rule": "fbjs-createNodesFromMarkup",
|
||||
"path": "node_modules/create-react-context/node_modules/fbjs/lib/createNodesFromMarkup.js",
|
||||
"line": "function createNodesFromMarkup(markup, handleScript) {",
|
||||
"lineNumber": 51,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-04-26T19:18:14.550Z"
|
||||
},
|
||||
{
|
||||
"rule": "fbjs-createNodesFromMarkup",
|
||||
"path": "node_modules/create-react-context/node_modules/fbjs/lib/createNodesFromMarkup.js",
|
||||
"line": " !!!dummyNode ? process.env.NODE_ENV !== 'production' ? invariant(false, 'createNodesFromMarkup dummy not initialized') : invariant(false) : void 0;",
|
||||
"lineNumber": 53,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-04-26T19:18:14.550Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/create-react-context/node_modules/fbjs/lib/createNodesFromMarkup.js",
|
||||
"line": " node.innerHTML = wrap[1] + markup + wrap[2];",
|
||||
"lineNumber": 58,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-04-26T19:18:14.550Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/create-react-context/node_modules/fbjs/lib/createNodesFromMarkup.js",
|
||||
"line": " node.innerHTML = markup;",
|
||||
"lineNumber": 65,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-04-26T19:18:14.550Z"
|
||||
},
|
||||
{
|
||||
"rule": "fbjs-createNodesFromMarkup",
|
||||
"path": "node_modules/create-react-context/node_modules/fbjs/lib/createNodesFromMarkup.js",
|
||||
"line": " !handleScript ? process.env.NODE_ENV !== 'production' ? invariant(false, 'createNodesFromMarkup(...): Unexpected <script> element rendered.') : invariant(false) : void 0;",
|
||||
"lineNumber": 70,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-04-26T19:18:14.550Z"
|
||||
},
|
||||
{
|
||||
"rule": "fbjs-createNodesFromMarkup",
|
||||
"path": "node_modules/create-react-context/node_modules/fbjs/lib/createNodesFromMarkup.js",
|
||||
"line": "module.exports = createNodesFromMarkup;",
|
||||
"lineNumber": 81,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-04-26T19:18:14.550Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/create-react-context/node_modules/fbjs/lib/getMarkupWrap.js",
|
||||
"line": " * Some browsers cannot use `innerHTML` to render certain elements standalone,",
|
||||
"lineNumber": 23,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-04-26T19:18:14.550Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/create-react-context/node_modules/fbjs/lib/getMarkupWrap.js",
|
||||
"line": " dummyNode.innerHTML = '<link />';",
|
||||
"lineNumber": 83,
|
||||
"reasonCategory": "falseMatch"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/create-react-context/node_modules/fbjs/lib/getMarkupWrap.js",
|
||||
"line": " dummyNode.innerHTML = '<' + nodeName + '></' + nodeName + '>';",
|
||||
"lineNumber": 85,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-04-26T19:18:14.550Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-prepend(",
|
||||
"path": "node_modules/css/node_modules/source-map/lib/source-map/source-node.js",
|
||||
|
@ -2919,6 +2848,230 @@
|
|||
"reasonCategory": "falseMatch",
|
||||
"updated": "2018-09-19T21:59:32.770Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/dist/Draft.js",
|
||||
"line": "\t var blockNode = ReactDOM.findDOMNode(this);",
|
||||
"lineNumber": 3773,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/draft-js/dist/Draft.js",
|
||||
"line": "\t doc.documentElement.innerHTML = html;",
|
||||
"lineNumber": 5535,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/dist/Draft.js",
|
||||
"line": "\t var editorNode = ReactDOM.findDOMNode(_this.editor);",
|
||||
"lineNumber": 7047,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/dist/Draft.js",
|
||||
"line": "\t var editorNode = ReactDOM.findDOMNode(_this.editor);",
|
||||
"lineNumber": 7081,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/draft-js/dist/Draft.js",
|
||||
"line": "\t * Resetting innerHTML will move focus to the beginning of the editor,",
|
||||
"lineNumber": 7570,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/dist/Draft.js",
|
||||
"line": "\t var node = ReactDOM.findDOMNode(this);",
|
||||
"lineNumber": 8140,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/dist/Draft.js",
|
||||
"line": "\t var leafNode = ReactDOM.findDOMNode(this.leaf);",
|
||||
"lineNumber": 8168,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/dist/Draft.js",
|
||||
"line": "\t var node = ReactDOM.findDOMNode(this);",
|
||||
"lineNumber": 8406,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/dist/Draft.js",
|
||||
"line": "\t var editorNode = ReactDOM.findDOMNode(editor.editorContainer);",
|
||||
"lineNumber": 10608,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-outerHTML",
|
||||
"path": "node_modules/draft-js/dist/Draft.js",
|
||||
"line": "\t return anonymized.outerHTML;",
|
||||
"lineNumber": 12689,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/dist/Draft.min.js",
|
||||
"lineNumber": 16,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "node_modules/draft-js/dist/Draft.min.js",
|
||||
"lineNumber": 16,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/draft-js/dist/Draft.min.js",
|
||||
"lineNumber": 17,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/dist/Draft.min.js",
|
||||
"lineNumber": 17,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/dist/Draft.min.js",
|
||||
"lineNumber": 18,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-outerHTML",
|
||||
"path": "node_modules/draft-js/dist/Draft.min.js",
|
||||
"lineNumber": 19,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/lib/DraftEditor.react.js",
|
||||
"line": " var editorNode = ReactDOM.findDOMNode(_this.editor);",
|
||||
"lineNumber": 82,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/lib/DraftEditor.react.js",
|
||||
"line": " var editorNode = ReactDOM.findDOMNode(_this.editor);",
|
||||
"lineNumber": 116,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/lib/DraftEditorBlock.react.js",
|
||||
"line": " var blockNode = ReactDOM.findDOMNode(this);",
|
||||
"lineNumber": 92,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/lib/DraftEditorBlockNode.react.js",
|
||||
"line": " var blockNode = ReactDOM.findDOMNode(this);",
|
||||
"lineNumber": 215,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/draft-js/lib/DraftEditorCompositionHandler.js",
|
||||
"line": " * Resetting innerHTML will move focus to the beginning of the editor,",
|
||||
"lineNumber": 128,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/lib/DraftEditorLeaf.react.js",
|
||||
"line": " var node = ReactDOM.findDOMNode(this);",
|
||||
"lineNumber": 72,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/lib/DraftEditorLeaf.react.js",
|
||||
"line": " var leafNode = ReactDOM.findDOMNode(this.leaf);",
|
||||
"lineNumber": 100,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/lib/DraftEditorTextNode.react.js",
|
||||
"line": " var node = ReactDOM.findDOMNode(this);",
|
||||
"lineNumber": 84,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "React-findDOMNode",
|
||||
"path": "node_modules/draft-js/lib/editOnSelect.js",
|
||||
"line": " var editorNode = ReactDOM.findDOMNode(editor.editorContainer);",
|
||||
"lineNumber": 28,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/draft-js/lib/getSafeBodyFromHTML.js",
|
||||
"line": " doc.documentElement.innerHTML = html;",
|
||||
"lineNumber": 33,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-outerHTML",
|
||||
"path": "node_modules/draft-js/lib/setDraftEditorSelection.js",
|
||||
"line": " return anonymized.outerHTML;",
|
||||
"lineNumber": 33,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "node_modules/draft-js/node_modules/immutable/dist/immutable.min.js",
|
||||
"lineNumber": 11,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "node_modules/draft-js/node_modules/immutable/dist/immutable.min.js",
|
||||
"lineNumber": 23,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-wrap(",
|
||||
"path": "node_modules/duplexer3/index.js",
|
||||
|
@ -3390,6 +3543,27 @@
|
|||
"reasonCategory": "falseMatch",
|
||||
"updated": "2018-11-27T18:02:26.186Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "node_modules/immutable/dist/immutable.min.js",
|
||||
"lineNumber": 9,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "node_modules/immutable/dist/immutable.min.js",
|
||||
"lineNumber": 21,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "node_modules/immutable/dist/immutable.min.js",
|
||||
"lineNumber": 34,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-$(",
|
||||
"path": "node_modules/intl-tel-input/build/js/intlTelInput.js",
|
||||
|
@ -5003,6 +5177,78 @@
|
|||
"reasonCategory": "falseMatch",
|
||||
"updated": "2018-09-19T18:13:29.628Z"
|
||||
},
|
||||
{
|
||||
"rule": "fbjs-createNodesFromMarkup",
|
||||
"path": "node_modules/prop-types/node_modules/fbjs/lib/createNodesFromMarkup.js",
|
||||
"line": "function createNodesFromMarkup(markup, handleScript) {",
|
||||
"lineNumber": 51,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "fbjs-createNodesFromMarkup",
|
||||
"path": "node_modules/prop-types/node_modules/fbjs/lib/createNodesFromMarkup.js",
|
||||
"line": " !!!dummyNode ? process.env.NODE_ENV !== 'production' ? invariant(false, 'createNodesFromMarkup dummy not initialized') : invariant(false) : void 0;",
|
||||
"lineNumber": 53,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/prop-types/node_modules/fbjs/lib/createNodesFromMarkup.js",
|
||||
"line": " node.innerHTML = wrap[1] + markup + wrap[2];",
|
||||
"lineNumber": 58,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/prop-types/node_modules/fbjs/lib/createNodesFromMarkup.js",
|
||||
"line": " node.innerHTML = markup;",
|
||||
"lineNumber": 65,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "fbjs-createNodesFromMarkup",
|
||||
"path": "node_modules/prop-types/node_modules/fbjs/lib/createNodesFromMarkup.js",
|
||||
"line": " !handleScript ? process.env.NODE_ENV !== 'production' ? invariant(false, 'createNodesFromMarkup(...): Unexpected <script> element rendered.') : invariant(false) : void 0;",
|
||||
"lineNumber": 70,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "fbjs-createNodesFromMarkup",
|
||||
"path": "node_modules/prop-types/node_modules/fbjs/lib/createNodesFromMarkup.js",
|
||||
"line": "module.exports = createNodesFromMarkup;",
|
||||
"lineNumber": 81,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/prop-types/node_modules/fbjs/lib/getMarkupWrap.js",
|
||||
"line": " * Some browsers cannot use `innerHTML` to render certain elements standalone,",
|
||||
"lineNumber": 23,
|
||||
"reasonCategory": "falseMatch",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/prop-types/node_modules/fbjs/lib/getMarkupWrap.js",
|
||||
"line": " dummyNode.innerHTML = '<link />';",
|
||||
"lineNumber": 83,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "DOM-innerHTML",
|
||||
"path": "node_modules/prop-types/node_modules/fbjs/lib/getMarkupWrap.js",
|
||||
"line": " dummyNode.innerHTML = '<' + nodeName + '></' + nodeName + '>';",
|
||||
"lineNumber": 85,
|
||||
"reasonCategory": "usageTrusted",
|
||||
"updated": "2019-06-20T20:21:33.456Z"
|
||||
},
|
||||
{
|
||||
"rule": "eval",
|
||||
"path": "node_modules/protobufjs/dist/light/protobuf.js",
|
||||
|
|
77
yarn.lock
|
@ -14,6 +14,13 @@
|
|||
dependencies:
|
||||
regenerator-runtime "^0.12.0"
|
||||
|
||||
"@babel/runtime@^7.2.0":
|
||||
version "7.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12"
|
||||
integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.2"
|
||||
|
||||
"@journeyapps/sqlcipher@https://github.com/scottnonnenberg-signal/node-sqlcipher.git#2e28733b61640556b0272a3bfc78b0357daf71e6":
|
||||
version "3.2.1"
|
||||
resolved "https://github.com/scottnonnenberg-signal/node-sqlcipher.git#2e28733b61640556b0272a3bfc78b0357daf71e6"
|
||||
|
@ -94,6 +101,14 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/config/-/config-0.0.34.tgz#123f91bdb5afdd702294b9de9ca04d9ea11137b0"
|
||||
integrity sha512-jWi9DXx77hnzN4kHCNEvP/kab+nchRLTg9yjXYxjTcMBkuc5iBb3QuwJ4sPrb+nzy1GQjrfyfMqZOdR4i7opRQ==
|
||||
|
||||
"@types/draft-js@0.10.32":
|
||||
version "0.10.32"
|
||||
resolved "https://registry.yarnpkg.com/@types/draft-js/-/draft-js-0.10.32.tgz#cbfed40c500d9bb1e486813dde73125291e7517d"
|
||||
integrity sha512-x63qkMUVpK8lAdxYQFk54F92F1mcG202qs4t0I+UqsZyCpkRRJlMQ+1v3QBbPcckVlmmdSg7m2PqoAbeXNjCaA==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
immutable "^3.8.1"
|
||||
|
||||
"@types/events@*":
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7"
|
||||
|
@ -219,6 +234,13 @@
|
|||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-measure@2.0.5":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-measure/-/react-measure-2.0.5.tgz#c1d304e3cab3a1c393342bf377b040628e6c29a8"
|
||||
integrity sha512-T1Bpt8FlWbDhoInUaNrjTOiVRpRJmrRcqhFJxLGBq1VjaqBLHCvUPapgdKMWEIX4Oqsa1SSKjtNkNJGy6WAAZg==
|
||||
dependencies:
|
||||
"@types/react" "*"
|
||||
|
||||
"@types/react-redux@7.0.1":
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/react-redux/-/react-redux-7.0.1.tgz#9dd2503be7a9861c5a092bf1c5050b7ade4dc62e"
|
||||
|
@ -2577,6 +2599,15 @@ dotenv@^6.2.0:
|
|||
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-6.2.0.tgz#941c0410535d942c8becf28d3f357dbd9d476064"
|
||||
integrity sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==
|
||||
|
||||
draft-js@0.10.5:
|
||||
version "0.10.5"
|
||||
resolved "https://registry.yarnpkg.com/draft-js/-/draft-js-0.10.5.tgz#bfa9beb018fe0533dbb08d6675c371a6b08fa742"
|
||||
integrity sha512-LE6jSCV9nkPhfVX2ggcRLA4FKs6zWq9ceuO/88BpXdNCS7mjRTgs0NsV6piUCJX9YxMsB9An33wnkMmU2sD2Zg==
|
||||
dependencies:
|
||||
fbjs "^0.8.15"
|
||||
immutable "~3.7.4"
|
||||
object-assign "^4.1.0"
|
||||
|
||||
dtrace-provider@~0.8:
|
||||
version "0.8.7"
|
||||
resolved "https://registry.yarnpkg.com/dtrace-provider/-/dtrace-provider-0.8.7.tgz#dc939b4d3e0620cfe0c1cd803d0d2d7ed04ffd04"
|
||||
|
@ -2779,6 +2810,11 @@ emoji-js@3.4.0:
|
|||
dependencies:
|
||||
emoji-datasource "4.0.0"
|
||||
|
||||
emoji-regex@8.0.0:
|
||||
version "8.0.0"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
|
||||
integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
|
||||
|
||||
"emoji-regex@>=6.0.0 <=6.1.1":
|
||||
version "6.1.1"
|
||||
resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e"
|
||||
|
@ -3359,7 +3395,7 @@ faye-websocket@~0.11.0:
|
|||
dependencies:
|
||||
websocket-driver ">=0.5.1"
|
||||
|
||||
fbjs@^0.8.0:
|
||||
fbjs@^0.8.0, fbjs@^0.8.15:
|
||||
version "0.8.17"
|
||||
resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd"
|
||||
integrity sha1-xNWY6taUkRJlPWWIsBpc3Nn5D90=
|
||||
|
@ -3733,7 +3769,7 @@ functional-red-black-tree@^1.0.1:
|
|||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327"
|
||||
|
||||
fuse.js@^3.4.4:
|
||||
fuse.js@3.4.4:
|
||||
version "3.4.4"
|
||||
resolved "https://registry.yarnpkg.com/fuse.js/-/fuse.js-3.4.4.tgz#f98f55fcb3b595cf6a3e629c5ffaf10982103e95"
|
||||
integrity sha512-pyLQo/1oR5Ywf+a/tY8z4JygnIglmRxVUOiyFAbd11o9keUDpUJSMGRWJngcnkURj30kDHPmhoKY8ChJiz3EpQ==
|
||||
|
@ -3770,6 +3806,11 @@ get-func-name@^2.0.0:
|
|||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/get-func-name/-/get-func-name-2.0.0.tgz#ead774abee72e20409433a066366023dd6887a41"
|
||||
|
||||
get-node-dimensions@^1.2.1:
|
||||
version "1.2.1"
|
||||
resolved "https://registry.yarnpkg.com/get-node-dimensions/-/get-node-dimensions-1.2.1.tgz#fb7b4bb57060fb4247dd51c9d690dfbec56b0823"
|
||||
integrity sha512-2MSPMu7S1iOTL+BOa6K1S62hB2zUAYNF/lV0gSVlOaacd087lc6nR1H1r0e3B1CerTo+RceOmi1iJW+vp21xcQ==
|
||||
|
||||
get-own-enumerable-property-symbols@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz#5c4ad87f2834c4b9b4e84549dc1e0650fb38c24b"
|
||||
|
@ -4546,6 +4587,16 @@ immediate@~3.0.5:
|
|||
version "3.0.6"
|
||||
resolved "https://registry.yarnpkg.com/immediate/-/immediate-3.0.6.tgz#9db1dbd0faf8de6fbe0f5dd5e56bb606280de69b"
|
||||
|
||||
immutable@^3.8.1:
|
||||
version "3.8.2"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.2.tgz#c2439951455bb39913daf281376f1530e104adf3"
|
||||
integrity sha1-wkOZUUVbs5kT2vKBN28VMOEErfM=
|
||||
|
||||
immutable@~3.7.4:
|
||||
version "3.7.6"
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.7.6.tgz#13b4d3cb12befa15482a26fe1b2ebae640071e4b"
|
||||
integrity sha1-E7TTyxK++hVIKib+Gy665kAHHks=
|
||||
|
||||
import-lazy@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-2.1.0.tgz#05698e3d45c88e8d7e9d92cb0584e77f096f3e43"
|
||||
|
@ -7758,7 +7809,17 @@ react-lifecycles-compat@^3.0.4:
|
|||
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||
|
||||
react-popper@^1.3.3:
|
||||
react-measure@2.3.0:
|
||||
version "2.3.0"
|
||||
resolved "https://registry.yarnpkg.com/react-measure/-/react-measure-2.3.0.tgz#75835d39abec9ae13517f35a819c160997a7a44e"
|
||||
integrity sha512-dwAvmiOeblj5Dvpnk8Jm7Q8B4THF/f1l1HtKVi0XDecsG6LXwGvzV5R1H32kq3TW6RW64OAf5aoQxpIgLa4z8A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.2.0"
|
||||
get-node-dimensions "^1.2.1"
|
||||
prop-types "^15.6.2"
|
||||
resize-observer-polyfill "^1.5.0"
|
||||
|
||||
react-popper@1.3.3:
|
||||
version "1.3.3"
|
||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.3.tgz#2c6cef7515a991256b4f0536cd4bdcb58a7b6af6"
|
||||
integrity sha512-ynMZBPkXONPc5K4P5yFWgZx5JGAUIP3pGGLNs58cfAPgK67olx7fmLp+AdpZ0+GoQ+ieFDa/z4cdV6u7sioH6w==
|
||||
|
@ -8083,6 +8144,11 @@ regenerator-runtime@^0.12.0:
|
|||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de"
|
||||
integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg==
|
||||
|
||||
regenerator-runtime@^0.13.2:
|
||||
version "0.13.2"
|
||||
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.2.tgz#32e59c9a6fb9b1a4aff09b4930ca2d4477343447"
|
||||
integrity sha512-S/TQAZJO+D3m9xeN1WTI8dLKBBiRgXBlTJvbWjCThHWZj9EvHK70Ff50/tYj2J/fvBY6JtFVwRuazHN2E7M9BA==
|
||||
|
||||
regex-cache@^0.4.2:
|
||||
version "0.4.4"
|
||||
resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.4.tgz#75bdc58a2a1496cec48a12835bc54c8d562336dd"
|
||||
|
@ -8336,6 +8402,11 @@ reselect@4.0.0:
|
|||
resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7"
|
||||
integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA==
|
||||
|
||||
resize-observer-polyfill@^1.5.0:
|
||||
version "1.5.1"
|
||||
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||
|
||||
resolve-cwd@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
||||
|
|