diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 668afe7fd..d46d0674a 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -160,6 +160,15 @@ module ApplicationHelper
output.compact_blank.join(' ')
end
+ def theme_style_tags(theme)
+ if theme == 'system'
+ concat stylesheet_pack_tag('mastodon-light', media: 'not all and (prefers-color-scheme: dark)', crossorigin: 'anonymous')
+ concat stylesheet_pack_tag('default', media: '(prefers-color-scheme: dark)', crossorigin: 'anonymous')
+ else
+ stylesheet_pack_tag theme, media: 'all', crossorigin: 'anonymous'
+ end
+ end
+
def cdn_host
Rails.configuration.action_controller.asset_host
end
diff --git a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
index 7b917ac43..9d6ff5226 100644
--- a/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
+++ b/app/javascript/mastodon/features/emoji/__tests__/emoji-test.js
@@ -22,23 +22,23 @@ describe('emoji', () => {
it('does unicode', () => {
expect(emojify('\uD83D\uDC69\u200D\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66')).toEqual(
- '');
+ '');
expect(emojify('๐จโ๐ฉโ๐งโ๐ง')).toEqual(
- '');
- expect(emojify('๐ฉโ๐ฉโ๐ฆ')).toEqual('');
+ '');
+ expect(emojify('๐ฉโ๐ฉโ๐ฆ')).toEqual('');
expect(emojify('\u2757')).toEqual(
- '');
+ '');
});
it('does multiple unicode', () => {
expect(emojify('\u2757 #\uFE0F\u20E3')).toEqual(
- ' ');
+ ' ');
expect(emojify('\u2757#\uFE0F\u20E3')).toEqual(
- '');
+ '');
expect(emojify('\u2757 #\uFE0F\u20E3 \u2757')).toEqual(
- ' ');
+ ' ');
expect(emojify('foo \u2757 #\uFE0F\u20E3 bar')).toEqual(
- 'foo bar');
+ 'foo bar');
});
it('ignores unicode inside of tags', () => {
@@ -46,16 +46,16 @@ describe('emoji', () => {
});
it('does multiple emoji properly (issue 5188)', () => {
- expect(emojify('๐๐๐')).toEqual('');
- expect(emojify('๐ ๐ ๐')).toEqual(' ');
+ expect(emojify('๐๐๐')).toEqual('');
+ expect(emojify('๐ ๐ ๐')).toEqual(' ');
});
it('does an emoji that has no shortcode', () => {
- expect(emojify('๐โ๐จ')).toEqual('');
+ expect(emojify('๐โ๐จ')).toEqual('');
});
it('does an emoji whose filename is irregular', () => {
- expect(emojify('โ๏ธ')).toEqual('');
+ expect(emojify('โ๏ธ')).toEqual('');
});
it('avoid emojifying on invisible text', () => {
@@ -67,11 +67,11 @@ describe('emoji', () => {
it('avoid emojifying on invisible text with nested tags', () => {
expect(emojify('๐bar๐ด๐'))
- .toEqual('๐bar๐ด');
+ .toEqual('๐bar๐ด');
expect(emojify('๐๐๐ด๐'))
- .toEqual('๐๐๐ด');
+ .toEqual('๐๐๐ด');
expect(emojify('๐
๐ด๐'))
- .toEqual('๐
๐ด');
+ .toEqual('๐
๐ด');
});
it('does not emojify emojis with textual presentation VS15 character', () => {
@@ -79,19 +79,19 @@ describe('emoji', () => {
.toEqual('โด๏ธ');
});
- it('does an simple emoji properly', () => {
+ it('does a simple emoji properly', () => {
expect(emojify('โโ'))
- .toEqual('');
+ .toEqual('');
});
it('does an emoji containing ZWJ properly', () => {
expect(emojify('๐โโ๏ธ๐โโ๏ธ'))
- .toEqual('');
+ .toEqual('');
});
it('keeps ordering as expected (issue fixed by PR 20677)', () => {
expect(emojify('
๐ #foo test: foo.
')) - .toEqual('#foo test: foo.
'); + .toEqual('#foo test: foo.
'); }); }); }); diff --git a/app/javascript/mastodon/features/emoji/emoji.js b/app/javascript/mastodon/features/emoji/emoji.js index 5918a65ed..e4aad302f 100644 --- a/app/javascript/mastodon/features/emoji/emoji.js +++ b/app/javascript/mastodon/features/emoji/emoji.js @@ -17,8 +17,13 @@ const emojiFilenames = (emojis) => { const darkEmoji = emojiFilenames(['๐ฑ', '๐', 'โซ', '๐ค', 'โฌ', 'โผ๏ธ', 'โพ', 'โผ๏ธ', 'โ๏ธ', 'โช๏ธ', '๐ฃ', '๐ณ', '๐ท', '๐ธ', 'โฃ๏ธ', '๐ถ๏ธ', 'โด๏ธ', '๐', '๐โโ๏ธ', '๐ฝ๏ธ', '๐ณ', '๐ฆ', '๐', '๐ช', '๐ณ๏ธ', '๐น๏ธ', '๐', '๐๏ธ', '๐๏ธ', '๐โโ๏ธ', '๐ค', '๐', '๐ฅ', '๐ผ', 'โ ๏ธ', '๐ฉ', '๐ฆ', '๐ผ', '๐น', '๐ฎ', '๐', '๐ด', '๐', '๐บ', '๐ฑ', '๐ฒ', '๐ฒ', '๐ชฎ', '๐ฆโโฌ']); const lightEmoji = emojiFilenames(['๐ฝ', 'โพ', '๐', 'โ๏ธ', '๐จ', '๐๏ธ', '๐', '๐ฅ', '๐ป', '๐', 'โ', 'โ', 'โธ๏ธ', '๐ฉ๏ธ', '๐', '๐', '๐', '๐ง๏ธ', '๐', '๐', '๐', '๐', '๐', '๐', 'โ ๏ธ', '๐จ๏ธ', '๐', '๐', '๐ฌ', '๐ญ', '๐', '๐ณ๏ธ', 'โช', 'โฌ', 'โฝ', 'โป๏ธ', 'โซ๏ธ', '๐ชฝ', '๐ชฟ']); -const emojiFilename = (filename) => { - const borderedEmoji = (document.body && document.body.classList.contains('theme-mastodon-light')) ? lightEmoji : darkEmoji; +/** + * @param {string} filename + * @param {"light" | "dark" } colorScheme + * @returns {string} + */ +const emojiFilename = (filename, colorScheme) => { + const borderedEmoji = colorScheme === "light" ? lightEmoji : darkEmoji; return borderedEmoji.includes(filename) ? (filename + '_border') : filename; }; @@ -92,12 +97,30 @@ const emojifyTextNode = (node, customEmojis) => { const { filename, shortCode } = unicodeMapping[unicode_emoji]; const title = shortCode ? `:${shortCode}:` : ''; - replacement = document.createElement('img'); - replacement.setAttribute('draggable', 'false'); - replacement.setAttribute('class', 'emojione'); - replacement.setAttribute('alt', unicode_emoji); - replacement.setAttribute('title', title); - replacement.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename)}.svg`); + replacement = document.createElement('picture'); + + const isSystemTheme = !!document.body?.classList.contains('theme-system'); + + if(isSystemTheme) { + let source = document.createElement('source'); + source.setAttribute('media', '(prefers-color-scheme: dark)'); + source.setAttribute('srcset', `${assetHost}/emoji/${emojiFilename(filename, "dark")}.svg`); + replacement.appendChild(source); + } + + let img = document.createElement('img'); + img.setAttribute('draggable', 'false'); + img.setAttribute('class', 'emojione'); + img.setAttribute('alt', unicode_emoji); + img.setAttribute('title', title); + + let theme = "light"; + + if(!isSystemTheme && !document.body?.classList.contains('theme-mastodon-light')) + theme = "dark"; + + img.setAttribute('src', `${assetHost}/emoji/${emojiFilename(filename, theme)}.svg`); + replacement.appendChild(img); } // Add the processed-up-to-now string and the emoji replacement diff --git a/app/lib/themes.rb b/app/lib/themes.rb index 243ffb9ab..4010d8443 100644 --- a/app/lib/themes.rb +++ b/app/lib/themes.rb @@ -11,6 +11,6 @@ class Themes end def names - @conf.keys + ['system'] + @conf.keys end end diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 449657f8c..0cd7fc9f4 100755 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -27,7 +27,7 @@ %title= html_title = stylesheet_pack_tag 'common', media: 'all', crossorigin: 'anonymous' - = stylesheet_pack_tag current_theme, media: 'all', crossorigin: 'anonymous' + = theme_style_tags current_theme -# Needed for the wicg-inert polyfill. It needs to be on it's own