Change how theme style packs are loaded

Load the `common` style pack, and then charge the style pack for the current
skin, independent from any selected JS pack.
This commit is contained in:
Claire 2024-04-27 22:34:41 +02:00
parent 93cdc66e64
commit c3e12a4dfa
18 changed files with 82 additions and 84 deletions

View file

@ -19,6 +19,7 @@ class ApplicationController < ActionController::Base
helper_method :current_session helper_method :current_session
helper_method :current_flavour helper_method :current_flavour
helper_method :current_skin helper_method :current_skin
helper_method :current_theme
helper_method :single_user_mode? helper_method :single_user_mode?
helper_method :use_seamless_external_login? helper_method :use_seamless_external_login?
helper_method :omniauth_only? helper_method :omniauth_only?

View file

@ -4,7 +4,7 @@ module ThemingConcern
extend ActiveSupport::Concern extend ActiveSupport::Concern
def use_pack(pack_name) def use_pack(pack_name)
@theme = resolve_pack_with_common(Themes.instance.flavour(current_flavour), pack_name, current_skin) @theme = resolve_pack(Themes.instance.flavour(current_flavour), pack_name)
end end
private private
@ -18,29 +18,22 @@ module ThemingConcern
[current_user&.setting_skin, Setting.skin, 'default'].find { |skin| skins.include?(skin) } [current_user&.setting_skin, Setting.skin, 'default'].find { |skin| skins.include?(skin) }
end end
def valid_pack_data?(data, pack_name) def current_theme
data['pack'].is_a?(Hash) && data['pack'][pack_name].present? # NOTE: this is slightly different from upstream, as it's a derived value used
# for the sole purpose of pointing to the appropriate stylesheet pack
"skins/#{current_flavour}/#{current_skin}"
end end
def nil_pack(data) def resolve_pack(data, pack_name)
{ pack_data = {
flavour: data['name'], flavour: data['name'],
pack: nil, pack: nil,
preload: nil, preload: nil,
skin: nil,
supported_locales: data['locales'],
}
end
def pack(data, pack_name, skin)
pack_data = {
flavour: data['name'],
pack: pack_name,
preload: nil,
skin: nil,
supported_locales: data['locales'], supported_locales: data['locales'],
} }
return pack_data unless data['pack'].is_a?(Hash) && data['pack'][pack_name].present?
pack_data[:pack] = pack_name
return pack_data unless data['pack'][pack_name].is_a?(Hash) return pack_data unless data['pack'][pack_name].is_a?(Hash)
pack_data[:pack] = nil unless data['pack'][pack_name]['filename'] pack_data[:pack] = nil unless data['pack'][pack_name]['filename']
@ -49,22 +42,6 @@ module ThemingConcern
pack_data[:preload] = [preloads] if preloads.is_a?(String) pack_data[:preload] = [preloads] if preloads.is_a?(String)
pack_data[:preload] = preloads if preloads.is_a?(Array) pack_data[:preload] = preloads if preloads.is_a?(Array)
if skin != 'default' && data['skin'][skin]
pack_data[:skin] = skin if data['skin'][skin].include?(pack_name)
elsif data['pack'][pack_name]['stylesheet']
pack_data[:skin] = 'default'
end
pack_data pack_data
end end
def resolve_pack(data, pack_name, skin)
pack(data, pack_name, skin) if valid_pack_data?(data, pack_name)
end
def resolve_pack_with_common(data, pack_name, skin = 'default')
result = resolve_pack(data, pack_name, skin) || nil_pack(data)
result[:common] = resolve_pack(data, 'common', skin)
result
end
end end

View file

@ -0,0 +1,12 @@
import Rails from '@rails/ujs';
import 'font-awesome/css/font-awesome.css';
export function start() {
require.context('@/images/', true);
try {
Rails.start();
} catch (e) {
// If called twice
}
}

View file

@ -1,8 +1,12 @@
import 'packs/public-path'; import 'packs/public-path';
import { start } from 'flavours/glitch/common';
import { loadLocale } from 'flavours/glitch/locales'; import { loadLocale } from 'flavours/glitch/locales';
import main from "flavours/glitch/main"; import main from "flavours/glitch/main";
import { loadPolyfills } from 'flavours/glitch/polyfills'; import { loadPolyfills } from 'flavours/glitch/polyfills';
start();
loadPolyfills() loadPolyfills()
.then(loadLocale) .then(loadLocale)
.then(main) .then(main)

View file

@ -1,9 +1,8 @@
/* This file is a hack to have something more reliable than the upstream `common` tag
that is implicitly generated as the common chunk through webpack's `splitChunks` config */
import 'packs/public-path'; import 'packs/public-path';
import 'font-awesome/css/font-awesome.css'; import 'font-awesome/css/font-awesome.css';
import Rails from '@rails/ujs';
import 'flavours/glitch/styles/index.scss';
Rails.start(); // This is a hack to ensures that webpack compiles our images.
// This ensures that webpack compiles our images.
require.context('../images', true); require.context('../images', true);

View file

@ -10,6 +10,7 @@ import Rails from '@rails/ujs';
import axios from 'axios'; import axios from 'axios';
import { throttle } from 'lodash'; import { throttle } from 'lodash';
import { start } from 'flavours/glitch/common';
import { timeAgoString } from 'flavours/glitch/components/relative_timestamp'; import { timeAgoString } from 'flavours/glitch/components/relative_timestamp';
import emojify from 'flavours/glitch/features/emoji/emoji'; import emojify from 'flavours/glitch/features/emoji/emoji';
import loadKeyboardExtensions from 'flavours/glitch/load_keyboard_extensions'; import loadKeyboardExtensions from 'flavours/glitch/load_keyboard_extensions';
@ -19,6 +20,8 @@ import ready from 'flavours/glitch/ready';
import 'cocoon-js-vanilla'; import 'cocoon-js-vanilla';
start();
const messages = defineMessages({ const messages = defineMessages({
usernameTaken: { usernameTaken: {
id: 'username.taken', id: 'username.taken',

View file

@ -1,10 +1,13 @@
import 'packs/public-path'; import 'packs/public-path';
import { createRoot } from 'react-dom/client'; import { createRoot } from 'react-dom/client';
import { start } from 'flavours/glitch/common';
import ComposeContainer from 'flavours/glitch/containers/compose_container'; import ComposeContainer from 'flavours/glitch/containers/compose_container';
import { loadPolyfills } from 'flavours/glitch/polyfills'; import { loadPolyfills } from 'flavours/glitch/polyfills';
import ready from 'flavours/glitch/ready'; import ready from 'flavours/glitch/ready';
start();
function loaded() { function loaded() {
const mountNode = document.getElementById('mastodon-compose'); const mountNode = document.getElementById('mastodon-compose');

View file

@ -1,5 +1,5 @@
/* This file is a hack to have something more reliable than the upstream `common` tag
that is implicitly generated as the common chunk through webpack's `splitChunks` config */
import './public-path'; import './public-path';
import 'font-awesome/css/font-awesome.css'; import 'font-awesome/css/font-awesome.css';
import 'styles/application.scss';
require.context('../images/', true);

View file

@ -0,0 +1 @@
@import 'flavours/glitch/styles/index';

View file

@ -0,0 +1 @@
@import 'styles/application';

View file

@ -7,7 +7,8 @@ class Themes
include Singleton include Singleton
def initialize def initialize
result = {} @flavours = {}
Rails.root.glob('app/javascript/flavours/*/theme.yml') do |pathname| Rails.root.glob('app/javascript/flavours/*/theme.yml') do |pathname|
data = YAML.load_file(pathname) data = YAML.load_file(pathname)
next unless data['pack'] next unless data['pack']
@ -35,42 +36,34 @@ class Themes
data['name'] = name data['name'] = name
data['locales'] = locales data['locales'] = locales
data['screenshot'] = screenshots data['screenshot'] = screenshots
data['skin'] = { 'default' => [] } data['skins'] = []
result[name] = data @flavours[name] = data
end end
Rails.root.glob('app/javascript/skins/*/*') do |pathname| Rails.root.glob('app/javascript/skins/*/*') do |pathname|
ext = pathname.extname.to_s ext = pathname.extname.to_s
skin = pathname.basename.to_s skin = pathname.basename.to_s
name = pathname.dirname.basename.to_s name = pathname.dirname.basename.to_s
next unless result[name] next unless @flavours[name]
if pathname.directory? if pathname.directory?
pack = [] @flavours[name]['skins'] << skin if pathname.glob('{common,index,application}.{css,scss}').any?
pathname.glob('*.{css,scss}') do |sheet|
pack.push(sheet.basename(sheet.extname).to_s)
end
elsif /^\.s?css$/i.match?(ext) elsif /^\.s?css$/i.match?(ext)
skin = pathname.basename(ext).to_s @flavours[name]['skins'] << pathname.basename(ext).to_s
pack = ['common']
end end
result[name]['skin'][skin] = pack if skin != 'default'
end end
@conf = result
end end
def flavour(name) def flavour(name)
@conf[name] @flavours[name]
end end
def flavours def flavours
@conf.keys @flavours.keys
end end
def skins_for(name) def skins_for(name)
@conf[name]['skin'].keys @flavours[name]['skins']
end end
def flavours_and_skins def flavours_and_skins

View file

@ -1,12 +1,5 @@
- if theme - if theme && theme[:pack]
= render partial: 'layouts/theme', object: theme[:common] if theme[:pack] != 'common' && theme[:common]
- if theme[:pack]
- pack_path = "flavours/#{theme[:flavour]}/#{theme[:pack]}" - pack_path = "flavours/#{theme[:flavour]}/#{theme[:pack]}"
= javascript_pack_tag pack_path, crossorigin: 'anonymous' = javascript_pack_tag pack_path, crossorigin: 'anonymous'
- if theme[:skin]
- if !theme[:flavour] || theme[:skin] == 'default'
= stylesheet_pack_tag pack_path, media: 'all', crossorigin: 'anonymous'
- else
= stylesheet_pack_tag "skins/#{theme[:flavour]}/#{theme[:skin]}/#{theme[:pack]}", media: 'all', crossorigin: 'anonymous'
- theme[:preload]&.each do |link| - theme[:preload]&.each do |link|
%link{ href: asset_pack_path("#{link}.js"), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/ %link{ href: asset_pack_path("#{link}.js"), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/

View file

@ -26,11 +26,13 @@
%title= html_title %title= html_title
= javascript_pack_tag 'common', crossorigin: 'anonymous' = stylesheet_pack_tag 'flavours/vanilla/common', media: 'all', crossorigin: 'anonymous' # upstream uses `common` but that's implicitly defined
= stylesheet_pack_tag current_theme, media: 'all', crossorigin: 'anonymous'
-# Needed for the wicg-inert polyfill. It needs to be on it's own <style> tag, with this `id` -# Needed for the wicg-inert polyfill. It needs to be on it's own <style> tag, with this `id`
= stylesheet_pack_tag 'flavours/vanilla/inert', media: 'all', id: 'inert-style' = stylesheet_pack_tag 'flavours/vanilla/inert', media: 'all', id: 'inert-style'
= javascript_pack_tag 'common', crossorigin: 'anonymous'
- if @theme - if @theme
- if @theme[:supported_locales].include? I18n.locale.to_s - if @theme[:supported_locales].include? I18n.locale.to_s
= preload_pack_asset "locales/#{@theme[:flavour]}/#{I18n.locale}-json.js" = preload_pack_asset "locales/#{@theme[:flavour]}/#{I18n.locale}-json.js"
@ -39,13 +41,13 @@
= csrf_meta_tags unless skip_csrf_meta_tags? = csrf_meta_tags unless skip_csrf_meta_tags?
%meta{ name: 'style-nonce', content: request.content_security_policy_nonce } %meta{ name: 'style-nonce', content: request.content_security_policy_nonce }
= stylesheet_link_tag custom_css_path, skip_pipeline: true, host: root_url, media: 'all'
= yield :header_tags = yield :header_tags
-# These must come after :header_tags to ensure our initial state has been defined. -# These must come after :header_tags to ensure our initial state has been defined.
= render partial: 'layouts/theme', object: @theme = render partial: 'layouts/theme', object: @theme
= stylesheet_link_tag custom_css_path, skip_pipeline: true, host: root_url, media: 'all'
%body{ class: body_classes } %body{ class: body_classes }
= content_for?(:content) ? yield(:content) : yield = content_for?(:content) ? yield(:content) : yield

View file

@ -11,13 +11,16 @@
- if storage_host? - if storage_host?
%link{ rel: 'dns-prefetch', href: storage_host }/ %link{ rel: 'dns-prefetch', href: storage_host }/
= render_initial_state = stylesheet_pack_tag 'flavours/vanilla/common', media: 'all', crossorigin: 'anonymous' # upstream uses `common` but that's implicitly defined
= javascript_pack_tag 'common', crossorigin: 'anonymous' = stylesheet_pack_tag current_theme, media: 'all', crossorigin: 'anonymous'
= javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous'
- if @theme - if @theme
- if @theme[:supported_locales].include? I18n.locale.to_s - if @theme[:supported_locales].include? I18n.locale.to_s
= preload_pack_asset "locales/#{@theme[:flavour]}/#{I18n.locale}-json.js" = preload_pack_asset "locales/#{@theme[:flavour]}/#{I18n.locale}-json.js"
- elsif @theme[:supported_locales].include? 'en' - elsif @theme[:supported_locales].include? 'en'
= preload_pack_asset "locales/#{@theme[:flavour]}/en-json.js" = preload_pack_asset "locales/#{@theme[:flavour]}/en-json.js"
= preload_pack_asset "locale/#{I18n.locale}-json.js"
= render_initial_state
= render partial: 'layouts/theme', object: @theme = render partial: 'layouts/theme', object: @theme
%body.embed %body.embed

View file

@ -5,6 +5,8 @@
%meta{ charset: 'utf-8' }/ %meta{ charset: 'utf-8' }/
%title= safe_join([yield(:page_title), Setting.default_settings['site_title']], ' - ') %title= safe_join([yield(:page_title), Setting.default_settings['site_title']], ' - ')
%meta{ content: 'width=device-width,initial-scale=1', name: 'viewport' }/ %meta{ content: 'width=device-width,initial-scale=1', name: 'viewport' }/
= stylesheet_pack_tag 'flavours/vanilla/common', media: 'all', crossorigin: 'anonymous' # upstream uses `common` but that's implicitly defined
= stylesheet_pack_tag current_theme, media: 'all', crossorigin: 'anonymous'
= javascript_pack_tag 'common', crossorigin: 'anonymous' = javascript_pack_tag 'common', crossorigin: 'anonymous'
= render partial: 'layouts/theme', object: @theme || { pack: 'error', flavour: 'glitch', common: { pack: 'common', flavour: 'glitch', skin: 'default' } } = render partial: 'layouts/theme', object: @theme || { pack: 'error', flavour: 'glitch', common: { pack: 'common', flavour: 'glitch', skin: 'default' } }
%body.error %body.error

View file

@ -1,7 +1,7 @@
// Common configuration for webpacker loaded from config/webpacker.yml // Common configuration for webpacker loaded from config/webpacker.yml
const { lstatSync, readFileSync } = require('fs'); const { lstatSync, readFileSync } = require('fs');
const { basename, dirname, extname, join, resolve } = require('path'); const { basename, dirname, join, resolve } = require('path');
const { env } = require('process'); const { env } = require('process');
const glob = require('glob'); const glob = require('glob');
@ -37,12 +37,13 @@ skinFiles.forEach((skinFile) => {
const data = flavours[name].skin; const data = flavours[name].skin;
if (lstatSync(skinFile).isDirectory()) { if (lstatSync(skinFile).isDirectory()) {
data[skin] = {}; data[skin] = {};
const skinPacks = glob.sync(join(skinFile, '*.{css,scss}')); // TODO: more cleanly take the first match
const skinPacks = glob.sync(join(skinFile, '{common,index,application}.{css,scss}'));
skinPacks.forEach((pack) => { skinPacks.forEach((pack) => {
data[skin][basename(pack, extname(pack))] = pack; data[skin] = pack;
}); });
} else if ((skin = skin.match(/^(.*)\.s?css$/i))) { } else if ((skin = skin.match(/^(.*)\.s?css$/i))) {
data[skin[1]] = { common: skinFile }; data[skin[1]] = skinFile;
} }
}); });

View file

@ -36,12 +36,7 @@ function reducePacks (data, into = {}) {
const skin = data.skin[skinName]; const skin = data.skin[skinName];
if (!skin) continue; if (!skin) continue;
for (const entry in skin) { into[`skins/${data.name}/${skinName}`] = resolve(skin);
const packFile = skin[entry];
if (!packFile) continue;
into[`skins/${data.name}/${skinName}/${entry}`] = resolve(packFile);
}
} }
return into; return into;

View file

@ -8,6 +8,14 @@ namespace :assets do
def current_user def current_user
nil nil
end end
def current_flavour
Setting.default_settings['flavour']
end
def current_skin
Setting.default_settings['skin']
end
end end
html = renderer.render(action, opts) html = renderer.render(action, opts)