mirror of https://github.com/zulip/zulip.git
emoji: Resolve emoji sprite sheets and stylesheets through Webpack.
This gives them cache-compatible URLs, and also avoids some extra copies of the sprite sheet images. Comments on the Octopus emoji added by tabbott. Signed-off-by: Anders Kaseorg <anders@zulipchat.com>
This commit is contained in:
parent
197084ab93
commit
1cdab5ae61
|
@ -1,3 +1,5 @@
|
|||
const rewiremock = require("rewiremock/node");
|
||||
|
||||
/*
|
||||
This test suite is designed to find errors
|
||||
in our initialization sequence. It doesn't
|
||||
|
@ -122,7 +124,15 @@ zrequire('top_left_corner');
|
|||
zrequire('starred_messages');
|
||||
zrequire('user_status');
|
||||
zrequire('user_status_ui');
|
||||
const ui_init = zrequire('ui_init');
|
||||
|
||||
const ui_init = rewiremock.proxy(
|
||||
() => zrequire("ui_init"),
|
||||
{
|
||||
"../../static/js/emojisets": {
|
||||
initialize: () => {},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
set_global('$', global.make_zjquery());
|
||||
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
/generated/bots/
|
||||
# From emoji
|
||||
/generated/emoji
|
||||
/generated/emoji-styles
|
||||
# From passing pygments data to the frontend
|
||||
/generated/pygments_data.json
|
||||
# From `tools/update-authors-json`
|
||||
|
|
|
@ -129,22 +129,6 @@ exports.initialize = function initialize() {
|
|||
}
|
||||
|
||||
exports.update_emojis(page_params.realm_emoji);
|
||||
|
||||
let emojiset = page_params.emojiset;
|
||||
if (page_params.emojiset === 'text') {
|
||||
// If the current emojiset is `text`, then we fallback to the
|
||||
// `google` emojiset on the backend (see zerver/views/home.py)
|
||||
// for displaying emojis in emoji picker and composebox
|
||||
// typeahead. This logic ensures that we do sprite sheet
|
||||
// prefetching for that case.
|
||||
emojiset = 'google-blob';
|
||||
}
|
||||
// Load the sprite image and octopus image in the background, so
|
||||
// that the browser will cache it for later use.
|
||||
const sprite = new Image();
|
||||
sprite.src = '/static/generated/emoji/sheet-' + emojiset + '-64.png';
|
||||
const octopus_image = new Image();
|
||||
octopus_image.src = '/static/generated/emoji/images-' + emojiset + '-64/1f419.png';
|
||||
};
|
||||
|
||||
exports.build_emoji_data = function (realm_emojis) {
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
import google_blob_css from "!style-loader?injectType=lazyStyleTag!css-loader!../generated/emoji-styles/google-blob-sprite.css";
|
||||
import google_blob_sheet from "emoji-datasource-google-blob/img/google/sheets-256/64.png";
|
||||
import google_css from "!style-loader?injectType=lazyStyleTag!css-loader!../generated/emoji-styles/google-sprite.css";
|
||||
import google_sheet from "emoji-datasource-google/img/google/sheets-256/64.png";
|
||||
import twitter_css from "!style-loader?injectType=lazyStyleTag!css-loader!../generated/emoji-styles/twitter-sprite.css";
|
||||
import twitter_sheet from "emoji-datasource-twitter/img/twitter/sheets-256/64.png";
|
||||
|
||||
const emojisets = new Map([
|
||||
["google", { css: google_css, sheet: google_sheet }],
|
||||
["google-blob", { css: google_blob_css, sheet: google_blob_sheet }],
|
||||
["twitter", { css: twitter_css, sheet: twitter_sheet }],
|
||||
]);
|
||||
|
||||
// For `text` emojiset we fallback to `google-blob` emojiset
|
||||
// for displaying emojis in emoji picker and typeahead.
|
||||
emojisets.set("text", emojisets.get("google-blob"));
|
||||
|
||||
let current_emojiset;
|
||||
|
||||
export async function select(name) {
|
||||
const new_emojiset = emojisets.get(name);
|
||||
if (new_emojiset === current_emojiset) {
|
||||
return;
|
||||
}
|
||||
await new Promise((resolve, reject) => {
|
||||
const sheet = new Image();
|
||||
sheet.onload = resolve;
|
||||
sheet.onerror = reject;
|
||||
sheet.src = new_emojiset.sheet;
|
||||
});
|
||||
if (current_emojiset) {
|
||||
current_emojiset.css.unuse();
|
||||
}
|
||||
new_emojiset.css.use();
|
||||
current_emojiset = new_emojiset;
|
||||
}
|
||||
|
||||
export function initialize() {
|
||||
select(page_params.emojiset);
|
||||
|
||||
// Load the octopus image in the background, so that the browser
|
||||
// will cache it for later use. Note that we hardcode the octopus
|
||||
// emoji to the old Google one because it's better.
|
||||
//
|
||||
// TODO: We should probably just make this work just like the Zulip emoji.
|
||||
const octopus_image = new Image();
|
||||
octopus_image.src = '/static/generated/emoji/images-google-64/1f419.png';
|
||||
}
|
|
@ -1,3 +1,4 @@
|
|||
const emojisets = require("./emojisets.js");
|
||||
const settings_config = require("./settings_config");
|
||||
|
||||
const meta = {
|
||||
|
@ -121,39 +122,24 @@ exports.set_up = function () {
|
|||
});
|
||||
};
|
||||
|
||||
exports.report_emojiset_change = function () {
|
||||
exports.report_emojiset_change = async function () {
|
||||
// TODO: Clean up how this works so we can use
|
||||
// change_display_setting. The challenge is that we don't want to
|
||||
// report success before the server_events request returns that
|
||||
// causes the actual sprite sheet to change. The current
|
||||
// implementation is wrong, though, in that it displays the UI
|
||||
// update in all active browser windows.
|
||||
function emoji_success() {
|
||||
if ($("#emoji-settings-status").length) {
|
||||
loading.destroy_indicator($("#emojiset_spinner"));
|
||||
$("#emojiset_select").val(page_params.emojiset);
|
||||
ui_report.success(i18n.t("Emojiset changed successfully!"),
|
||||
$('#emoji-settings-status').expectOne());
|
||||
const spinner = $("#emoji-settings-status").expectOne();
|
||||
settings_ui.display_checkmark(spinner);
|
||||
}
|
||||
|
||||
await emojisets.select(page_params.emojiset);
|
||||
|
||||
if ($("#emoji-settings-status").length) {
|
||||
loading.destroy_indicator($("#emojiset_spinner"));
|
||||
$("#emojiset_select").val(page_params.emojiset);
|
||||
ui_report.success(i18n.t("Emojiset changed successfully!"),
|
||||
$('#emoji-settings-status').expectOne());
|
||||
const spinner = $("#emoji-settings-status").expectOne();
|
||||
settings_ui.display_checkmark(spinner);
|
||||
}
|
||||
|
||||
let emojiset = page_params.emojiset;
|
||||
|
||||
if (page_params.emojiset === 'text') {
|
||||
// For `text` emojiset we fallback to `google-blob` emojiset
|
||||
// for displaying emojis in emoji picker and typeahead.
|
||||
emojiset = 'google-blob';
|
||||
}
|
||||
|
||||
const sprite = new Image();
|
||||
sprite.onload = function () {
|
||||
const sprite_css_href = "/static/generated/emoji/" + emojiset + "-sprite.css";
|
||||
$("#emoji-spritesheet").attr('href', sprite_css_href);
|
||||
emoji_success();
|
||||
};
|
||||
sprite.src = "/static/generated/emoji/sheet-" + emojiset + "-64.png";
|
||||
};
|
||||
|
||||
exports.update_page = function () {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
const emojisets = require("./emojisets");
|
||||
const markdown_config = require('./markdown_config');
|
||||
|
||||
// This is where most of our initialization takes place.
|
||||
|
@ -295,6 +296,7 @@ exports.initialize_kitchen_sink_stuff = function () {
|
|||
|
||||
exports.initialize_everything = function () {
|
||||
// initialize other stuff
|
||||
emojisets.initialize();
|
||||
people.initialize();
|
||||
scroll_bar.initialize();
|
||||
message_viewport.initialize();
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
{% block customhead %}
|
||||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<link href="/static/images/logo/apple-touch-icon-precomposed.png" rel="apple-touch-icon-precomposed">
|
||||
<link id="emoji-spritesheet" href="/static/generated/emoji/{{ emojiset }}-sprite.css" rel="stylesheet" type="text/css">
|
||||
<style>
|
||||
#app-loading {
|
||||
background-color: hsl(0, 0%, 100%);
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
{% set entrypoint = "archive" %}
|
||||
|
||||
{% block customhead %}
|
||||
<link id="emoji-spritesheet" href="/static/generated/emoji/google-sprite.css" rel="stylesheet" type="text/css">
|
||||
<style>
|
||||
.portico-header {
|
||||
padding-bottom: 0px;
|
||||
|
|
|
@ -20,6 +20,7 @@ sys.path.append(ZULIP_PATH)
|
|||
from scripts.lib.zulip_tools import generate_sha1sum_emoji
|
||||
|
||||
TARGET_EMOJI_DUMP = os.path.join(ZULIP_PATH, 'static', 'generated', 'emoji')
|
||||
TARGET_EMOJI_STYLES = os.path.join(ZULIP_PATH, 'static', 'generated', 'emoji-styles')
|
||||
EMOJI_CACHE_PATH = "/srv/zulip-emoji-cache"
|
||||
EMOJI_SCRIPT_DIR_PATH = os.path.join(ZULIP_PATH, 'tools', 'setup', 'emoji')
|
||||
NODE_MODULES_PATH = os.path.join(ZULIP_PATH, 'node_modules')
|
||||
|
@ -42,7 +43,7 @@ div.emoji,
|
|||
span.emoji
|
||||
{
|
||||
display: inline-block;
|
||||
background-image: url('sheet-%(emojiset)s-64.png');
|
||||
background-image: url(~emoji-datasource-%(emojiset)s/img/%(alt_name)s/sheets-256/64.png);
|
||||
background-size: %(background_size)s;
|
||||
background-repeat: no-repeat;
|
||||
|
||||
|
@ -54,7 +55,7 @@ span.emoji
|
|||
|
||||
.emoji-1f419
|
||||
{
|
||||
background-image: url('images-google-64/1f419.png') !important;
|
||||
background-image: url(../emoji/images-google-64/1f419.png) !important;
|
||||
background-position: 0%% 0%% !important;
|
||||
background-size: contain !important;
|
||||
}
|
||||
|
@ -90,6 +91,16 @@ def main() -> None:
|
|||
os.remove(TARGET_EMOJI_DUMP)
|
||||
os.symlink(source_emoji_dump, TARGET_EMOJI_DUMP)
|
||||
|
||||
# These must not be symlinked so webpack can resolve module references.
|
||||
os.makedirs(TARGET_EMOJI_STYLES, exist_ok=True)
|
||||
to_remove = set(os.listdir(TARGET_EMOJI_STYLES))
|
||||
for filename in os.listdir(source_emoji_dump):
|
||||
if filename.endswith(".css"):
|
||||
shutil.copy2(os.path.join(source_emoji_dump, filename), TARGET_EMOJI_STYLES)
|
||||
to_remove.discard(filename)
|
||||
for filename in to_remove:
|
||||
os.remove(os.path.join(TARGET_EMOJI_STYLES, filename))
|
||||
|
||||
def get_success_stamp() -> str:
|
||||
sha1_hexdigest = generate_sha1sum_emoji(ZULIP_PATH)
|
||||
return os.path.join(EMOJI_CACHE_PATH, sha1_hexdigest, 'emoji', '.success-stamp')
|
||||
|
@ -116,7 +127,8 @@ def get_square_size(emoji_data: List[Dict[str, Any]]) -> int:
|
|||
|
||||
def generate_sprite_css_files(cache_path: str,
|
||||
emoji_data: List[Dict[str, Any]],
|
||||
emojiset: str) -> None:
|
||||
emojiset: str,
|
||||
alt_name: str) -> None:
|
||||
"""
|
||||
Spritesheets are usually NxN squares.
|
||||
"""
|
||||
|
@ -197,6 +209,7 @@ def generate_sprite_css_files(cache_path: str,
|
|||
SPRITE_CSS_PATH = os.path.join(cache_path, '%s-sprite.css' % (emojiset,))
|
||||
with open(SPRITE_CSS_PATH, 'w') as f:
|
||||
f.write(SPRITE_CSS_FILE_TEMPLATE % {'emojiset': emojiset,
|
||||
'alt_name': alt_name,
|
||||
'emoji_positions': emoji_positions,
|
||||
'background_size': background_size,
|
||||
})
|
||||
|
@ -245,19 +258,13 @@ def setup_emoji_farms(cache_path: str, emoji_data: List[Dict[str, Any]]) -> None
|
|||
for f in os.listdir(zulip_image):
|
||||
shutil.copy2(os.path.join(zulip_image, f), target_emoji_farm, follow_symlinks=False)
|
||||
|
||||
# Copy spritesheets.
|
||||
emoji_data_path = os.path.join(NODE_MODULES_PATH, 'emoji-datasource-' + emojiset)
|
||||
input_sprite_sheet = os.path.join(emoji_data_path, 'img', alt_name, 'sheets-256', '64.png')
|
||||
output_sprite_sheet = os.path.join(cache_path, 'sheet-%s-64.png' % (emojiset,))
|
||||
shutil.copyfile(input_sprite_sheet, output_sprite_sheet)
|
||||
|
||||
# We hardcode octopus emoji image to Google emojiset's old
|
||||
# "cute octopus" image. Copy it to the emoji farms.
|
||||
input_img_file = os.path.join(EMOJI_SCRIPT_DIR_PATH, '1f419.png')
|
||||
output_img_file = os.path.join(cache_path, 'images-' + emojiset + '-64', '1f419.png')
|
||||
shutil.copyfile(input_img_file, output_img_file)
|
||||
|
||||
generate_sprite_css_files(cache_path, emoji_data, emojiset)
|
||||
generate_sprite_css_files(cache_path, emoji_data, emojiset, alt_name)
|
||||
|
||||
# Setup standard emojisets.
|
||||
for emojiset in ['google', 'twitter']:
|
||||
|
|
|
@ -29,7 +29,7 @@ with open(EMOJI_MAP_FILE) as fp:
|
|||
EMOJI_MAP = ujson.load(fp)
|
||||
|
||||
EMOJI_IMAGE_TEMPLATE = """
|
||||
<div class="emoji emoji-%(emoji_code)s %(emojiset)s" title=%(emojiset)s></div>
|
||||
<img class="emoji" src="images-%(emojiset)s-64/%(emoji_code)s.png" title=%(emojiset)s>
|
||||
"""
|
||||
|
||||
TABLE_ROW_TEMPLATE = """
|
||||
|
@ -47,7 +47,6 @@ TABLE_ROW_TEMPLATE = """
|
|||
EMOJI_LISTING_TEMPLATE = """
|
||||
<html>
|
||||
<head>
|
||||
<link rel = "stylesheet" type = "text/css" href = "/static/generated/emoji/google-sprite.css" />
|
||||
<style>
|
||||
%(table_css)s
|
||||
</style>
|
||||
|
|
|
@ -52,6 +52,7 @@ EXEMPT_FILES = {
|
|||
'static/js/drafts.js',
|
||||
'static/js/echo.js',
|
||||
'static/js/emoji_picker.js',
|
||||
'static/js/emojisets.js',
|
||||
'static/js/favicon.js',
|
||||
'static/js/feedback_widget.js',
|
||||
'static/js/floating_recipient_bar.js',
|
||||
|
|
|
@ -60,9 +60,16 @@ if args.authors_not_required:
|
|||
run(authors_cmd, stdout=fp, stderr=fp)
|
||||
|
||||
# Collect the files that we're going to serve; this creates prod-static/serve.
|
||||
run(['./manage.py', 'collectstatic', '--no-default-ignore',
|
||||
'--noinput', '-i', 'assets', '-i', 'html', '-i', 'js', '-i', 'styles', '-i', 'templates'],
|
||||
stdout=fp, stderr=fp)
|
||||
run([
|
||||
'./manage.py', 'collectstatic', '--no-default-ignore',
|
||||
'--noinput',
|
||||
'-i', 'assets',
|
||||
'-i', 'emoji-styles',
|
||||
'-i', 'html',
|
||||
'-i', 'js',
|
||||
'-i', 'styles',
|
||||
'-i', 'templates',
|
||||
], stdout=fp, stderr=fp)
|
||||
|
||||
# Compile translation strings to generate `.mo` files.
|
||||
run(['./manage.py', 'compilemessages'], stdout=fp, stderr=fp)
|
||||
|
|
|
@ -20,7 +20,8 @@
|
|||
"katex/dist/katex.css",
|
||||
"./static/styles/rendered_markdown.scss",
|
||||
"./static/styles/zulip.scss",
|
||||
"./static/styles/portico/archive.scss"
|
||||
"./static/styles/portico/archive.scss",
|
||||
"./static/generated/emoji-styles/google-sprite.css"
|
||||
],
|
||||
"billing": [
|
||||
"./static/js/bundles/portico.js",
|
||||
|
|
|
@ -121,7 +121,7 @@ export default (env?: string): webpack.Configuration[] => {
|
|||
use: [{
|
||||
loader: 'file-loader',
|
||||
options: {
|
||||
name: production ? '[name].[hash].[ext]' : '[name].[ext]',
|
||||
name: production ? '[name].[hash].[ext]' : '[path][name].[ext]',
|
||||
outputPath: 'files/',
|
||||
},
|
||||
}],
|
||||
|
|
|
@ -26,4 +26,4 @@ LATEST_RELEASE_ANNOUNCEMENT = "https://blog.zulip.org/2019/12/13/zulip-2-1-relea
|
|||
# historical commits sharing the same major version, in which case a
|
||||
# minor version bump suffices.
|
||||
|
||||
PROVISION_VERSION = '73.2'
|
||||
PROVISION_VERSION = '74.0'
|
||||
|
|
|
@ -927,18 +927,6 @@ class HomeTest(ZulipTestCase):
|
|||
page_params = self._get_page_params(result)
|
||||
self.assertEqual(page_params['default_language'], 'es')
|
||||
|
||||
def test_emojiset(self) -> None:
|
||||
user = self.example_user("hamlet")
|
||||
user.emojiset = 'text'
|
||||
user.save()
|
||||
self.login(user.email)
|
||||
result = self._get_home_page()
|
||||
self.assertEqual(result.status_code, 200)
|
||||
|
||||
html = result.content.decode('utf-8')
|
||||
self.assertIn('google-blob-sprite.css', html)
|
||||
self.assertNotIn('text-sprite.css', html)
|
||||
|
||||
def test_compute_show_invites_and_add_streams_admin(self) -> None:
|
||||
user = self.example_user("iago")
|
||||
|
||||
|
|
|
@ -305,20 +305,11 @@ def home_real(request: HttpRequest) -> HttpResponse:
|
|||
|
||||
csp_nonce = generate_random_token(48)
|
||||
if user_profile is not None:
|
||||
if user_profile.emojiset == UserProfile.TEXT_EMOJISET:
|
||||
# If current emojiset is `TEXT_EMOJISET`, then fallback to
|
||||
# GOOGLE_EMOJISET for picking which spritesheet's CSS to
|
||||
# include (and thus how to display emojis in the emoji picker
|
||||
# and composebox typeahead).
|
||||
emojiset = UserProfile.GOOGLE_BLOB_EMOJISET
|
||||
else:
|
||||
emojiset = user_profile.emojiset
|
||||
night_mode = user_profile.night_mode
|
||||
is_guest = user_profile.is_guest
|
||||
is_realm_admin = user_profile.is_realm_admin
|
||||
show_webathena = user_profile.realm.webathena_enabled
|
||||
else: # nocoverage
|
||||
emojiset = UserProfile.GOOGLE_BLOB_EMOJISET
|
||||
night_mode = False
|
||||
is_guest = False
|
||||
is_realm_admin = False
|
||||
|
@ -328,7 +319,6 @@ def home_real(request: HttpRequest) -> HttpResponse:
|
|||
|
||||
response = render(request, 'zerver/app/index.html',
|
||||
context={'user_profile': user_profile,
|
||||
'emojiset': emojiset,
|
||||
'page_params': page_params,
|
||||
'csp_nonce': csp_nonce,
|
||||
'show_debug':
|
||||
|
|
Loading…
Reference in New Issue