emoji sur les roles
This commit is contained in:
parent
652014e524
commit
98888d0370
@ -27,8 +27,103 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
}
|
||||
}
|
||||
|
||||
// Emoji list for reactions
|
||||
const EMOJIS = ['๐', 'โค๏ธ', '๐', '๐ฎ', '๐ข', '๐ฅ', 'โ
', '๐', 'โ', '๐ก', '๐', '๐ฏ'];
|
||||
// Emoji list categorized
|
||||
const EMOJI_CATEGORIES = {
|
||||
'Smileys': ['๐', '๐', '๐', '๐', '๐', '๐
', '๐คฃ', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฅฐ', '๐', '๐คฉ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐คช', '๐', '๐ค', '๐ค', '๐คญ', '๐คซ', '๐ค', '๐ค', '๐คจ', '๐', '๐', '๐ถ', '๐', '๐', '๐', '๐ฌ', '๐คฅ', '๐', '๐', '๐ช', '๐คค', '๐ด', '๐ท', '๐ค', '๐ค', '๐คข', '๐คฎ', '๐คง', '๐ฅต', '๐ฅถ', '๐ฅด', '๐ต', '๐คฏ', '๐ค ', '๐ฅณ', '๐', '๐ค', '๐ง', '๐', '๐', '๐', 'โน๏ธ', '๐ฎ', '๐ฏ', '๐ฒ', '๐ณ', '๐ฅบ', '๐ฆ', '๐ง', '๐จ', '๐ฐ', '๐ฅ', '๐ข', '๐ญ', '๐ฑ', '๐', '๐ฃ', '๐', '๐', '๐ฉ', '๐ซ', '๐ฅฑ', '๐ค', '๐ก', '๐ ', '๐คฌ', '๐', '๐ฟ', '๐น', '๐บ', '๐', 'โ ๏ธ', '๐ฉ', '๐คก', '๐น', '๐บ', '๐ป', '๐ฝ', '๐พ', '๐ค', '๐บ', '๐ธ', '๐ป', '๐ผ', '๐ฝ', '๐', '๐ฟ', '๐พ'],
|
||||
'People': ['๐', '๐ค', '๐๏ธ', 'โ', '๐', '๐', '๐ค', 'โ๏ธ', '๐ค', '๐ค', '๐ค', '๐ค', '๐', '๐', '๐', '๐', '๐', 'โ๏ธ', '๐', '๐', 'โ', '๐', '๐ค', '๐ค', '๐', '๐', '๐', '๐คฒ', '๐ค', '๐', 'โ๏ธ', '๐
', '๐คณ', '๐ช', '๐ฆพ', '๐ฆต', '๐ฆฟ', '๐ฆถ', '๐', '๐ฆป', '๐', '๐ง ', '๐ฆท', '๐ฆด', '๐', '๐๏ธ', '๐
', '๐', '๐ถ', '๐ง', '๐ฆ', '๐ง', '๐ง', '๐ฑ', '๐จ', '๐ง', '๐ฉ', '๐ง', '๐ด', '๐ต', '๐ฎ', '๐ต๏ธ', '๐', '๐ท', '๐คด', '๐ธ', '๐ณ', '๐ฒ', '๐ง', '๐คต', '๐ฐ', '๐คฐ', '๐คฑ', '๐ผ', '๐
', '๐คถ', '๐ฆธ', '๐ฆน', '๐ง', '๐ง', '๐ง', '๐ง', '๐ง', '๐ง', '๐ง', '๐', '๐', '๐ถ', '๐', '๐', '๐บ', '๐ด๏ธ', '๐ฏ', '๐ง', '๐ง'],
|
||||
'Animals': ['๐ถ', '๐ฑ', '๐ญ', '๐น', '๐ฐ', '๐ฆ', '๐ป', '๐ผ', '๐จ', '๐ฏ', '๐ฆ', '๐ฎ', '๐ท', '๐ฝ', '๐ธ', '๐ต', '๐', '๐', '๐', '๐', '๐', '๐ง', '๐ฆ', '๐ค', '๐ฃ', '๐ฅ', '๐ฆ', '๐ฆ
', '๐ฆ', '๐ฆ', '๐บ', '๐', '๐ด', '๐ฆ', '๐', '๐', '๐ฆ', '๐', '๐', '๐', '๐ฆ', '๐ฆ', '๐ท๏ธ', '๐ธ๏ธ', '่ ', '๐ข', '๐', '๐ฆ', '๐ฆ', '๐ฆ', '๐', '๐ฆ', '๐ฆ', '๐ฆ', '๐ฆ', '๐ก', '๐ ', '๐', '๐ฌ', '๐ณ', '๐', '๐ฆ', '๐', '๐
', '๐', '๐ฆ', '๐ฆ', '๐ฆง', '๐', '๐ฆ', '๐ฆ', '๐ช', '๐ซ', '๐ฆ', '๐ฆ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฆ', '๐', '๐ฉ', '๐ฆฎ', '๐', '๐', '๐ฆ', '๐ฆ', '๐ฆ', '๐ฆข', '๐ฆฉ', '๐๏ธ', '๐', '๐ฆ', '๐ฆจ', '๐ฆก', '๐ฆฆ', '๐ฆฅ', '๐', '๐', '๐ฟ๏ธ', '๐ฆ'],
|
||||
'Nature': ['๐ต', '๐', '๐ฒ', '๐ณ', '๐ด', '๐ฑ', '๐ฟ', 'โ๏ธ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐พ', '๐', '๐ท', '๐น', '๐ฅ', '๐บ', '๐ธ', '๐ผ', '๐ป', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ช', '๐ซ', 'โญ๏ธ', '๐', 'โจ', 'โก๏ธ', 'โ๏ธ', '๐ฅ', '๐ฅ', '๐ช๏ธ', '๐', 'โ๏ธ', '๐ค๏ธ', 'โ
๏ธ', '๐ฅ๏ธ', 'โ๏ธ', '๐ฆ๏ธ', '๐ง๏ธ', '๐จ๏ธ', '๐ฉ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', '๐ฌ๏ธ', '๐จ', '๐ง', '๐ฆ', 'โ๏ธ', 'โ๏ธ', '๐', '๐ซ๏ธ'],
|
||||
'Food': ['๐', '๐', '๐', '๐', '๐', ' BANANA', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฅญ', '๐', '๐ฅฅ', '๐ฅ', '๐
', '๐', '๐ฅ', '๐ฅฆ', '๐ฅฌ', '๐ฅ', '๐ฝ', '๐ฅ', '๐ง', '๐ฅ', '๐ ', '๐ฅ', '๐ฅฏ', '๐', '๐ฅ', '๐ฅจ', '๐ง', '๐ฅ', '๐ณ', '๐ง', '๐ฅ', ' waffle', '๐ฅ', '๐ฅฉ', '๐', '๐', '๐ฆด', '๐ญ', '๐', '๐', '๐', '๐ฅช', '๐ฅ', '๐ง', '๐ฎ', '๐ฏ', '๐ฅ', '๐ฅ', '๐', '๐', '๐ฒ', '๐', ' sushi', ' Bento', ' Dumpling', ' Oyster', '๐ค', ' Rice Ball', ' Rice', ' Rice Cracker', '๐ฅ', '๐ฅ ', '๐ฅฎ', '๐ข', '๐ก', '๐ง', '๐จ', '๐ฆ', '๐ฅง', '๐ง', '๐ฐ', '๐', '๐ฎ', '๐ญ', '๐ฌ', '๐ซ', ' popcorn', '๐ฉ', '๐ช', '๐ฐ', ' peanuts', '๐ฏ', '๐ฅ', 'โ๏ธ', '๐ต', '๐ฅค', '๐ถ', '๐บ', '๐ป', '๐ฅ', '๐ท', '๐ฅ', '๐ธ', '๐น', '๐ง', '๐พ', '๐ง', '๐ฅ', '๐ด', '๐ฝ๏ธ', '๐ฅฃ', '๐ฅก'],
|
||||
'Travel': ['๐', '๐', '๐', '๐', '๐', '๐๏ธ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ต', '๐ฒ', '๐ด', '๐', '๐ฃ๏ธ', '๐ค๏ธ', 'โฝ๏ธ', '๐จ', '๐ฅ', '๐ฆ', '๐ง', 'โ๏ธ', 'โต๏ธ', '๐ค', '๐ณ๏ธ', 'โด๏ธ', '๐ข', 'โ๏ธ', '๐ซ', '๐ฌ', '๐บ', '๐', '๐', '๐ ', '๐ก', '๐', '๐ธ', '๐ฐ๏ธ', 'โ๏ธ', 'โณ', 'โ๏ธ', 'โฐ', 'โฑ๏ธ', 'โฒ๏ธ', '๐ฐ๏ธ', '๐ก๏ธ', 'โ๏ธ', '๐ช', '๐', 'โ๏ธ', 'โ
๏ธ', 'โ๏ธ', '๐', 'โฐ๏ธ', '๐๏ธ', '๐ป', '๐', '๐๏ธ', '๐๏ธ', 'โบ๏ธ', '๐ ', '๐ก', '๐ข', '๐ฃ', '๐ค', '๐ฅ', '๐ฆ', '๐จ', '๐ฉ', '๐ช', '๐ซ', '๐ฌ', '๐ญ', '๐ฏ', '๐ฐ', '๐', '๐ผ', '๐ฝ', 'โช๏ธ', '๐', '๐', 'โฉ๏ธ', '๐', 'โฒ๏ธ', '๐', '๐', '๐๏ธ', '๐', '๐
', '๐', '๐', '๐', '๐ ', '๐ก', '๐ข', '๐', '๐', '๐', '๐
', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐'],
|
||||
'Activities': ['โฝ๏ธ', '๐', '๐', 'โพ๏ธ', '๐ฅ', '๐พ', '๐', '๐', '๐ฑ', '๐', '๐ธ', '๐ฅ
', ' hockey', ' field hockey', ' cricket', 'โณ๏ธ', '๐น', '๐ฃ', ' boxing', '๐ฅ', ' skateboard', '๐ท', 'โธ๏ธ', '๐ฅ', '๐ฟ', 'โท๏ธ', '๐', '๐๏ธ', ' fencing', '๐คผ', ' gymnastics', ' basketball player', '๐คฝ', ' handball', ' juggle', '๐ง', '๐', ' rowing', ' swimming', '๐ด', '๐ต', '๐ง', '๐๏ธ', '๐', '๐
', '๐ฅ', '๐ฅ', '๐ฅ', '๐ซ', '๐๏ธ', '๐ญ', '๐จ', '๐ฌ', '๐ค', '๐ง', '๐ผ', '๐น', '๐ฅ', '๐ท', '๐บ', '๐ธ', '๐ช', '๐ป', '๐ฒ', 'โ๏ธ', '๐ฏ', '๐ณ', '๐ฎ', '๐ฐ', '๐งฉ'],
|
||||
'Objects': ['โ๏ธ', '๐ฑ', '๐ฒ', '๐ป', 'โจ๏ธ', '๐ฑ๏ธ', '๐ฒ๏ธ', '๐น๏ธ', '๐๏ธ', '๐ฝ', '๐พ', '๐ฟ', '๐๏ธ', '๐ท', '๐ธ', '๐น', '๐ผ', '๐', '๐', '๐ฏ๏ธ', '๐ก', '๐ฆ', '๐ฎ', '๐ช', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐ฐ', '๐๏ธ', '๐', '๐', '๐ท๏ธ', '๐ฐ', '๐ด', '๐ต', '๐ถ', '๐ท', '๐ธ', '๐ณ', '๐งพ', '๐น', 'โ๏ธ', '๐ง', '๐จ', '๐ฉ', '๐ค', '๐ฅ', '๐ฆ', '๐ซ', '๐ฉ', '๐ฌ', '๐ญ', '๐ฎ', '๐ณ๏ธ', 'โ๏ธ', 'โ๏ธ', '๐๏ธ', '๐๏ธ', '๐๏ธ', '๐๏ธ', '๐', '๐ผ', '๐', '๐', '๐๏ธ', '๐
', '๐', '๐๏ธ', '๐๏ธ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐๏ธ', '๐', '๐', 'โ๏ธ', '๐๏ธ', '๐๏ธ', '๐๏ธ', '๐', '๐', '๐', '๐', '๐', '๐๏ธ', '๐จ', '๐ช', 'โ๏ธ', 'โ๏ธ', '๐ ๏ธ', '๐ก๏ธ', 'โ๏ธ', '๐ซ', '๐ก๏ธ', '๐ง', '๐ฉ', 'โ๏ธ', '๐๏ธ', 'โ๏ธ', '๐ฆฏ', '๐', 'โ๏ธ', '๐งฐ', '๐งฒ', 'โ๏ธ', '๐งช', '๐งซ', '๐งฌ', '๐ฌ', '๐ญ', '๐ก', '๐', '๐ฉธ', '๐', '๐ฉน', '๐ฉบ', '๐ช', '๐๏ธ', '๐๏ธ', '๐ช', '๐ฝ', '๐ฟ', '๐', '๐ช', '๐งด', '๐งท', '๐งน', '๐งบ', '๐งป', '๐งผ', '๐งฏ', '๐', '๐ฌ', 'โฐ๏ธ', 'โฑ๏ธ', '๐ฟ'],
|
||||
'Symbols': ['๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', 'โฃ๏ธ', '๐', 'โค๏ธ', '๐งก', '๐', '๐', '๐', '๐', '๐ค', '๐ค', '๐ค', '๐ฏ', '๐ข', '๐ฅ', '๐ซ', '๐ฆ', '๐จ', '๐ณ๏ธ', '๐ฃ', '๐ฌ', '๐๏ธโ๐จ๏ธ', '๐จ๏ธ', '๐ฏ๏ธ', '๐ญ', '๐ค', '๐', 'โ ๏ธ', 'โฅ๏ธ', 'โฆ๏ธ', 'โฃ๏ธ', '๐', '๐๏ธ', '๐ด', '๐ญ', '๐', '๐', '๐', '๐', '๐ข', '๐ฃ', '๐ฏ', '๐', '๐', '๐ผ', '๐ต', '๐ถ', '๐น', '๐ง', '๐ฎ', '๐ฐ', 'โฟ๏ธ', '๐น', '๐บ', '๐ป', '๐ผ', '๐พ', '๐', '๐', '๐', '๐
', 'โ ๏ธ', '๐ธ', 'โ๏ธ', '๐ซ', '๐ณ', '๐ญ', '๐ฏ', '๐ฑ', '๐ท', '๐ต', '๐', 'โข๏ธ', 'โฃ๏ธ', 'โฌ๏ธ', 'โ๏ธ', 'โก๏ธ', 'โ๏ธ', 'โฌ๏ธ', 'โ๏ธ', 'โฌ
๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โฉ๏ธ', 'โช๏ธ', 'โคด๏ธ', 'โคต๏ธ', '๐', '๐', '๐', '๐', '๐', '๐', '๐', '๐', 'โ๏ธ', '๐๏ธ', 'โก๏ธ', 'โธ๏ธ', 'โฏ๏ธ', 'โ๏ธ', 'โฆ๏ธ', 'โช๏ธ', 'โฎ๏ธ', '๐', '๐ฏ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ', '๐', '๐', '๐', 'โถ๏ธ', 'โฉ', 'โญ๏ธ', 'โฏ๏ธ', 'โ๏ธ', 'โช', 'โฎ๏ธ', '๐ผ', 'โซ', '๐ฝ', 'โฌ', 'โธ๏ธ', 'โน๏ธ', 'โบ๏ธ', 'โ๏ธ', '๐ฆ', '๐
', '๐', '๐ถ', '๐ณ', '๐ด', 'โ', 'โ', 'โ', 'โ๏ธ', 'โพ๏ธ', 'โผ๏ธ', 'โ๏ธ', 'โ', 'โ', 'โ', 'โ๏ธ', 'ใฐ๏ธ', '๐ฑ', '๐ฒ', 'โ๏ธ', 'โป๏ธ', 'โ๏ธ', '๐ฑ', '๐', '๐ฐ', 'โญ๏ธ', 'โ
', 'โ๏ธ', 'โ๏ธ', 'โ๏ธ', 'โ', 'โ', 'โฐ', 'โฟ', 'ใฝ๏ธ', 'โณ๏ธ', 'โด๏ธ', 'โ๏ธ', 'โผ๏ธ', '๐', '๐๏ธ', '๐ท๏ธ', '๐ถ', '๐ฏ๏ธ', '๐', '๐น', '๐๏ธ', '๐ฒ', '๐', '๐ธ', '๐ด', '๐ณ', 'ใ๏ธ', 'ใ๏ธ', '๐บ', '๐ต', '๐ด', '๐ ', '๐ก', '๐ข', '๐ต', '๐ฃ', '๐ค', 'โซ๏ธ', 'โช๏ธ', '๐ฅ', '๐ง', '๐จ', '๐ฉ', '๐ฆ', '๐ช', '๐ซ', 'โฌ๏ธ', 'โฌ๏ธ'],
|
||||
'Flags': ['๐', '๐ฉ', '๐', '๐ด', '๐ณ๏ธ', '๐ณ๏ธโ๐', '๐ณ๏ธโโง๏ธ', '๐ดโโ ๏ธ', '๐ฆ๐ซ', '๐ฆ๐ฝ', '๐ฆ๐ฑ', '๐ฉ๐ฟ', '๐ฆ๐ฒ', '๐ฆ๐บ', '๐ฆ๐น', '๐ฆ๐ฟ', '๐ง๐ช', '๐ง๐ท', '๐จ๐ฆ', '๐จ๐ฑ', '๐จ๐ณ', '๐จ๐ด', '๐จ๐ฟ', '๐ฉ๐ฐ', '๐ช๐ฌ', '๐ซ๐ฎ', '๐ซ๐ท', '๐ฉ๐ช', '๐ฌ๐ท', '๐ญ๐ฐ', '๐ฎ๐ณ', '๐ฎ๐ฉ', '๐ฎ๐ช', '๐ฎ๐ฑ', '๐ฎ๐น', '๐ฏ๐ต', '๐ฐ๐ท', '๐ฒ๐ฝ', '๐ณ๐ฑ', '๐ณ๐ฟ', '๐ณ๐ด', '๐ต๐ฐ', '๐ต๐ญ', '๐ต๐ฑ', '๐ต๐น', '๐ท๐บ', '๐ธ๐ฆ', '๐ธ๐ฌ', '๐ฟ๐ฆ', '๐ช๐ธ', '๐ธ๐ช', '๐จ๐ญ', '๐น๐ญ', '๐น๐ท', '๐บ๐ฆ', '๐ฆ๐ช', '๐ฌ๐ง', '๐บ๐ธ', '๐ป๐ณ']
|
||||
};
|
||||
|
||||
// Global EMOJIS list for reactions (flattened)
|
||||
const EMOJIS = Object.values(EMOJI_CATEGORIES).flat();
|
||||
|
||||
function populateEmojiGrid(category = null, searchTerm = '') {
|
||||
const roleEmojiGrid = document.getElementById('role-emoji-grid');
|
||||
if (!roleEmojiGrid) return;
|
||||
|
||||
roleEmojiGrid.innerHTML = '';
|
||||
let emojis = [];
|
||||
|
||||
if (searchTerm) {
|
||||
emojis = EMOJIS.filter(e => e.includes(searchTerm) || searchTerm === '');
|
||||
// For simple search by name, we'd need names, but since we only have the emoji characters,
|
||||
// the user might search by the emoji itself or we could just show all if search is empty.
|
||||
// Actually, without a mapping of emoji to names, searching is limited.
|
||||
// But I'll implement it anyway so if they paste an emoji it works,
|
||||
// or I can add common names if I had a mapping.
|
||||
// Given the constraint, I'll just filter by character for now,
|
||||
// or maybe the user just wants a way to filter the massive list.
|
||||
} else {
|
||||
emojis = EMOJI_CATEGORIES[category] || [];
|
||||
}
|
||||
|
||||
emojis.forEach(emoji => {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = emoji;
|
||||
span.style.cursor = 'pointer';
|
||||
span.style.fontSize = '20px';
|
||||
span.style.padding = '5px';
|
||||
span.style.textAlign = 'center';
|
||||
span.className = 'rounded role-emoji-item';
|
||||
span.onclick = () => {
|
||||
document.getElementById('edit-role-icon').value = emoji;
|
||||
document.getElementById('selected-role-emoji-preview').textContent = emoji;
|
||||
};
|
||||
roleEmojiGrid.appendChild(span);
|
||||
});
|
||||
}
|
||||
|
||||
function initEmojiCategories() {
|
||||
const categoriesContainer = document.getElementById('role-emoji-categories');
|
||||
const searchInput = document.getElementById('role-emoji-search');
|
||||
if (!categoriesContainer) return;
|
||||
|
||||
categoriesContainer.innerHTML = '';
|
||||
Object.keys(EMOJI_CATEGORIES).forEach((cat, index) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'btn btn-sm btn-dark text-nowrap px-2 py-1';
|
||||
btn.style.fontSize = '0.75em';
|
||||
btn.textContent = cat;
|
||||
if (index === 0) btn.classList.add('active', 'btn-primary');
|
||||
|
||||
btn.onclick = () => {
|
||||
if (searchInput) searchInput.value = '';
|
||||
categoriesContainer.querySelectorAll('button').forEach(b => b.classList.remove('active', 'btn-primary'));
|
||||
btn.classList.add('active', 'btn-primary');
|
||||
populateEmojiGrid(cat);
|
||||
};
|
||||
categoriesContainer.appendChild(btn);
|
||||
});
|
||||
|
||||
if (searchInput) {
|
||||
searchInput.oninput = () => {
|
||||
const term = searchInput.value.trim();
|
||||
if (term) {
|
||||
categoriesContainer.querySelectorAll('button').forEach(b => b.classList.remove('active', 'btn-primary'));
|
||||
populateEmojiGrid(null, term);
|
||||
} else {
|
||||
const activeCat = categoriesContainer.querySelector('button.active')?.textContent || Object.keys(EMOJI_CATEGORIES)[0];
|
||||
populateEmojiGrid(activeCat);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Initial load
|
||||
populateEmojiGrid(Object.keys(EMOJI_CATEGORIES)[0]);
|
||||
}
|
||||
|
||||
// Call init if elements exist
|
||||
if (document.getElementById('role-emoji-grid')) {
|
||||
initEmojiCategories();
|
||||
}
|
||||
|
||||
// Scroll to bottom
|
||||
scrollToBottom(true);
|
||||
@ -268,20 +363,100 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
function showEmojiPicker(anchor, callback) {
|
||||
document.querySelector('.emoji-picker')?.remove();
|
||||
const picker = document.createElement('div');
|
||||
picker.className = 'emoji-picker';
|
||||
EMOJIS.forEach(emoji => {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = emoji;
|
||||
span.onclick = () => {
|
||||
callback(emoji);
|
||||
picker.remove();
|
||||
picker.className = 'emoji-picker p-0 overflow-hidden d-flex flex-column';
|
||||
picker.style.width = '280px';
|
||||
picker.style.height = '320px';
|
||||
picker.style.backgroundColor = 'var(--bg-secondary)';
|
||||
picker.style.border = '1px solid var(--bg-tertiary)';
|
||||
picker.style.borderRadius = '8px';
|
||||
picker.style.boxShadow = '0 8px 24px rgba(0,0,0,0.5)';
|
||||
picker.style.zIndex = '2000';
|
||||
|
||||
const tabs = document.createElement('div');
|
||||
tabs.className = 'd-flex overflow-auto border-bottom border-secondary p-1 bg-dark';
|
||||
tabs.style.gap = '2px';
|
||||
|
||||
const searchContainer = document.createElement('div');
|
||||
searchContainer.className = 'p-2 border-bottom border-secondary';
|
||||
const searchInput = document.createElement('input');
|
||||
searchInput.type = 'text';
|
||||
searchInput.placeholder = 'Search emoji...';
|
||||
searchInput.className = 'form-control form-control-sm bg-dark border-secondary text-white';
|
||||
searchContainer.appendChild(searchInput);
|
||||
|
||||
const grid = document.createElement('div');
|
||||
grid.className = 'flex-grow-1 overflow-auto p-2';
|
||||
grid.style.display = 'grid';
|
||||
grid.style.gridTemplateColumns = 'repeat(7, 1fr)';
|
||||
grid.style.gap = '2px';
|
||||
|
||||
const renderGrid = (cat = null, term = '') => {
|
||||
grid.innerHTML = '';
|
||||
let emojis = [];
|
||||
if (term) {
|
||||
emojis = EMOJIS.filter(e => e.includes(term));
|
||||
} else {
|
||||
emojis = EMOJI_CATEGORIES[cat] || [];
|
||||
}
|
||||
|
||||
emojis.forEach(emoji => {
|
||||
const span = document.createElement('span');
|
||||
span.textContent = emoji;
|
||||
span.style.cursor = 'pointer';
|
||||
span.style.fontSize = '20px';
|
||||
span.style.padding = '5px';
|
||||
span.style.textAlign = 'center';
|
||||
span.className = 'rounded role-emoji-item';
|
||||
span.onclick = () => {
|
||||
callback(emoji);
|
||||
picker.remove();
|
||||
};
|
||||
grid.appendChild(span);
|
||||
});
|
||||
};
|
||||
|
||||
searchInput.oninput = () => {
|
||||
const term = searchInput.value.trim();
|
||||
if (term) {
|
||||
tabs.querySelectorAll('button').forEach(b => b.classList.remove('text-primary'));
|
||||
renderGrid(null, term);
|
||||
} else {
|
||||
const activeCat = tabs.querySelector('button.text-primary')?.textContent || Object.keys(EMOJI_CATEGORIES)[0];
|
||||
renderGrid(activeCat);
|
||||
}
|
||||
};
|
||||
|
||||
Object.keys(EMOJI_CATEGORIES).forEach((cat, index) => {
|
||||
const btn = document.createElement('button');
|
||||
btn.type = 'button';
|
||||
btn.className = 'btn btn-sm btn-dark text-nowrap px-2 py-1 border-0';
|
||||
btn.style.fontSize = '0.7em';
|
||||
btn.textContent = cat;
|
||||
if (index === 0) btn.classList.add('text-primary');
|
||||
|
||||
btn.onclick = (e) => {
|
||||
e.stopPropagation();
|
||||
searchInput.value = '';
|
||||
tabs.querySelectorAll('button').forEach(b => b.classList.remove('text-primary'));
|
||||
btn.classList.add('text-primary');
|
||||
renderGrid(cat);
|
||||
};
|
||||
picker.appendChild(span);
|
||||
tabs.appendChild(btn);
|
||||
});
|
||||
|
||||
picker.appendChild(tabs);
|
||||
picker.appendChild(searchContainer);
|
||||
picker.appendChild(grid);
|
||||
document.body.appendChild(picker);
|
||||
|
||||
renderGrid(Object.keys(EMOJI_CATEGORIES)[0]);
|
||||
|
||||
const rect = anchor.getBoundingClientRect();
|
||||
picker.style.top = `${rect.top - picker.offsetHeight - 5}px`;
|
||||
picker.style.left = `${rect.left}px`;
|
||||
let top = rect.top - picker.offsetHeight - 5;
|
||||
if (top < 0) top = rect.bottom + 5;
|
||||
picker.style.position = 'fixed';
|
||||
picker.style.top = `${top}px`;
|
||||
picker.style.left = `${Math.min(rect.left, window.innerWidth - 300)}px`;
|
||||
}
|
||||
|
||||
function updateReactionUI(messageId, reactions) {
|
||||
@ -436,7 +611,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
<div style="flex: 1;">
|
||||
<div class="message-author" style="font-size: 0.85em; ${authorStyle}">
|
||||
${escapeHTML(msg.username)}
|
||||
${msg.role_icon ? `<img src="${msg.role_icon}" class="role-icon ms-1" style="width: 16px; height: 16px; vertical-align: middle; object-fit: contain;">` : ''}
|
||||
${msg.role_icon ? `<img src="${msg.role_icon}" class="role-icon ms-1" style="width: 12px; height: 12px; vertical-align: middle; object-fit: contain;">` : ''}
|
||||
<span class="message-time">${msg.time}</span>
|
||||
</div>
|
||||
<div class="message-text" style="font-size: 0.9em;">
|
||||
@ -852,7 +1027,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
<div class="d-flex align-items-center">
|
||||
<div style="width: 14px; height: 14px; border-radius: 50%; background-color: ${role.color}; margin-right: 12px; box-shadow: 0 0 5px ${role.color}88;"></div>
|
||||
<span class="fw-medium">${role.name}</span>
|
||||
${role.icon_url ? (isUrl ? `<img src="${role.icon_url}" class="ms-1" style="width: 16px; height: 16px; object-fit: contain;">` : `<span class="ms-1" style="font-size: 14px;">${role.icon_url}</span>`) : ''}
|
||||
${role.icon_url ? (isUrl ? `<img src="${role.icon_url}" class="ms-1" style="width: 12px; height: 12px; object-fit: contain;">` : `<span class="ms-1" style="font-size: 12px;">${role.icon_url}</span>`) : ''}
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-sm btn-outline-light edit-role-btn-v2" data-id="${role.id}" data-name="${role.name}" data-color="${role.color}" data-perms="${role.permissions}" data-icon="${role.icon_url || ''}">Edit</button>
|
||||
@ -884,7 +1059,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
});
|
||||
|
||||
const isIconUrl = member.role_icon && (member.role_icon.startsWith('http') || member.role_icon.startsWith('/'));
|
||||
const roleIconHtml = member.role_icon ? (isIconUrl ? `<img src="${member.role_icon}" class="role-icon ms-1" style="width: 14px; height: 14px; vertical-align: middle; object-fit: contain;">` : `<span class="ms-1" style="font-size: 14px; vertical-align: middle;">${member.role_icon}</span>`) : '';
|
||||
const roleIconHtml = member.role_icon ? (isIconUrl ? `<img src="${member.role_icon}" class="role-icon ms-1" style="width: 12px; height: 12px; vertical-align: middle; object-fit: contain;">` : `<span class="ms-1" style="font-size: 12px; vertical-align: middle;">${member.role_icon}</span>`) : '';
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="d-flex align-items-center flex-grow-1">
|
||||
@ -912,6 +1087,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
document.getElementById('edit-role-name').value = role.name;
|
||||
document.getElementById('edit-role-color').value = role.color;
|
||||
document.getElementById('edit-role-icon').value = role.icon;
|
||||
document.getElementById('selected-role-emoji-preview').textContent = role.icon || '';
|
||||
|
||||
const permsContainer = document.getElementById('role-permissions-checkboxes');
|
||||
permsContainer.innerHTML = '';
|
||||
@ -957,12 +1133,6 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
} catch (e) { console.error(e); }
|
||||
});
|
||||
|
||||
document.getElementById('role-emoji-picker-btn')?.addEventListener('click', (e) => {
|
||||
showEmojiPicker(e.currentTarget, (emoji) => {
|
||||
document.getElementById('edit-role-icon').value = emoji;
|
||||
});
|
||||
});
|
||||
|
||||
membersList?.addEventListener('change', async (e) => {
|
||||
if (e.target.classList.contains('role-assign-check')) {
|
||||
const userId = e.target.dataset.userId;
|
||||
@ -1554,7 +1724,7 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
const authorStyle = msg.role_color ? `color: ${msg.role_color};` : '';
|
||||
|
||||
const isRoleIconUrl = msg.role_icon && (msg.role_icon.startsWith('http') || msg.role_icon.startsWith('/'));
|
||||
const roleIcon = msg.role_icon ? (isRoleIconUrl ? `<img src="${msg.role_icon}" class="role-icon ms-1" style="width: 20px; height: 20px; vertical-align: middle; object-fit: contain;">` : `<span class="ms-1" style="font-size: 20px; vertical-align: middle;">${msg.role_icon}</span>`) : '';
|
||||
const roleIcon = msg.role_icon ? (isRoleIconUrl ? `<img src="${msg.role_icon}" class="role-icon ms-1" style="width: 12px; height: 12px; vertical-align: middle; object-fit: contain;">` : `<span class="ms-1" style="font-size: 12px; vertical-align: middle;">${msg.role_icon}</span>`) : '';
|
||||
|
||||
div.innerHTML = `
|
||||
<div class="message-avatar" style="${avatarStyle}"></div>
|
||||
|
||||
33
index.php
33
index.php
@ -1,7 +1,7 @@
|
||||
<?php
|
||||
require_once 'auth/session.php';
|
||||
|
||||
function renderRoleIcon($icon, $size = '14px') {
|
||||
function renderRoleIcon($icon, $size = '12px') {
|
||||
if (empty($icon)) return '';
|
||||
$isUrl = (strpos($icon, 'http') === 0 || strpos($icon, '/') === 0);
|
||||
if ($isUrl) {
|
||||
@ -237,6 +237,11 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
background-color: <?php echo $channel_theme; ?>;
|
||||
}
|
||||
<?php endif; ?>
|
||||
.role-emoji-item:hover {
|
||||
background-color: rgba(255,255,255,0.1);
|
||||
transform: scale(1.2);
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body data-theme="<?php echo htmlspecialchars($user['theme'] ?? 'dark'); ?>">
|
||||
@ -495,7 +500,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<div class="message-content">
|
||||
<div class="message-author" style="<?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
|
||||
<?php echo htmlspecialchars($m['username']); ?>
|
||||
<?php echo renderRoleIcon($m['role_icon'], '20px'); ?>
|
||||
<?php echo renderRoleIcon($m['role_icon'], '12px'); ?>
|
||||
<span class="message-time"><?php echo date('H:i', strtotime($m['created_at'])); ?></span>
|
||||
<?php if ($is_solution): ?>
|
||||
<span class="badge bg-success ms-2">SOLUTION</span>
|
||||
@ -601,7 +606,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
<div class="thread-meta small text-muted">
|
||||
Started by <span style="<?php echo !empty($thread['role_color']) ? "color: {$thread['role_color']};" : ""; ?>"><?php echo htmlspecialchars($thread['username']); ?></span>
|
||||
<?php echo renderRoleIcon($thread['role_icon'], '14px'); ?>
|
||||
<?php echo renderRoleIcon($thread['role_icon'], '11px'); ?>
|
||||
โข <?php echo $thread['message_count']; ?> messages
|
||||
</div>
|
||||
</div>
|
||||
@ -631,7 +636,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<div class="message-header">
|
||||
<span class="message-author" style="<?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
|
||||
<?php echo htmlspecialchars($m['username']); ?>
|
||||
<?php echo renderRoleIcon($m['role_icon'], '20px'); ?>
|
||||
<?php echo renderRoleIcon($m['role_icon'], '12px'); ?>
|
||||
</span>
|
||||
<span class="message-time"><?php echo date('H:i', strtotime($m['created_at'])); ?></span>
|
||||
<?php if ($m['is_pinned']): ?>
|
||||
@ -779,7 +784,7 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
</div>
|
||||
<span style="overflow: hidden; text-overflow: ellipsis; white-space: nowrap; <?php echo !empty($m['role_color']) ? "color: {$m['role_color']};" : ""; ?>">
|
||||
<?php echo htmlspecialchars($m['username']); ?>
|
||||
<?php echo renderRoleIcon($m['role_icon'], '16px'); ?>
|
||||
<?php echo renderRoleIcon($m['role_icon'], '12px'); ?>
|
||||
</span>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
@ -1285,10 +1290,20 @@ $projectImageUrl = $_SERVER['PROJECT_IMAGE_URL'] ?? '';
|
||||
<input type="color" id="edit-role-color" class="form-control form-control-color w-100">
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Role Icon (Emoji or URL)</label>
|
||||
<div class="input-group">
|
||||
<input type="text" id="edit-role-icon" class="form-control" placeholder="https://example.com/icon.png">
|
||||
<button class="btn btn-outline-secondary" type="button" id="role-emoji-picker-btn">๐</button>
|
||||
<label class="form-label text-uppercase fw-bold" style="font-size: 0.7em; color: var(--text-muted);">Role Icon</label>
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div id="selected-role-emoji-preview" class="d-flex align-items-center justify-content-center border rounded" style="width: 40px; height: 40px; font-size: 24px; background: rgba(0,0,0,0.2);"></div>
|
||||
<input type="hidden" id="edit-role-icon">
|
||||
<button type="button" class="btn btn-sm btn-outline-danger ms-2" onclick="document.getElementById('edit-role-icon').value=''; document.getElementById('selected-role-emoji-preview').textContent='';">Remove Icon</button>
|
||||
</div>
|
||||
<div id="role-emoji-categories" class="d-flex overflow-auto mb-2 p-1" style="background: rgba(0,0,0,0.1); border-radius: 4px; gap: 5px;">
|
||||
<!-- Categories populated by JS -->
|
||||
</div>
|
||||
<div class="mb-2">
|
||||
<input type="text" id="role-emoji-search" class="form-control form-control-sm bg-dark border-secondary text-white" placeholder="Search emoji...">
|
||||
</div>
|
||||
<div id="role-emoji-grid" class="border rounded p-2" style="max-height: 150px; overflow-y: auto; display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; background: rgba(0,0,0,0.2);">
|
||||
<!-- Emojis populated by JS -->
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
|
||||
Loadingโฆ
x
Reference in New Issue
Block a user