diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 668afe7fde..d46d0674a4 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 7b917ac43b..9d6ff5226a 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 5918a65ed7..e4aad302f6 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 243ffb9ab9..4010d84435 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 449657f8ca..0cd7fc9f44 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