Finalized theme loading and stuff

pull/229/head
kibigo! 2017-11-20 22:13:37 -08:00
parent 321fa41930
commit bdbbd06dad
73 changed files with 573 additions and 378 deletions

View File

@ -1,6 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class AboutController < ApplicationController class AboutController < ApplicationController
before_action :set_pack
before_action :set_body_classes before_action :set_body_classes
before_action :set_instance_presenter, only: [:show, :more, :terms] before_action :set_instance_presenter, only: [:show, :more, :terms]
@ -21,6 +22,10 @@ class AboutController < ApplicationController
helper_method :new_user helper_method :new_user
def set_pack
use_pack action_name == 'show' ? 'about' : 'common'
end
def set_instance_presenter def set_instance_presenter
@instance_presenter = InstancePresenter.new @instance_presenter = InstancePresenter.new
end end

View File

@ -7,6 +7,7 @@ class AccountsController < ApplicationController
def show def show
respond_to do |format| respond_to do |format|
format.html do format.html do
use_pack 'public'
@pinned_statuses = [] @pinned_statuses = []
if current_account && @account.blocking?(current_account) if current_account && @account.blocking?(current_account)

View File

@ -4,8 +4,13 @@ module Admin
class BaseController < ApplicationController class BaseController < ApplicationController
include Authorization include Authorization
before_action :require_staff!
layout 'admin' layout 'admin'
before_action :require_staff!
before_action :set_pack
def set_pack
use_pack 'admin'
end
end end
end end

View File

@ -12,8 +12,6 @@ class ApplicationController < ActionController::Base
helper_method :current_account helper_method :current_account
helper_method :current_session helper_method :current_session
helper_method :current_theme
helper_method :theme_data
helper_method :single_user_mode? helper_method :single_user_mode?
rescue_from ActionController::RoutingError, with: :not_found rescue_from ActionController::RoutingError, with: :not_found
@ -54,6 +52,69 @@ class ApplicationController < ActionController::Base
new_user_session_path new_user_session_path
end end
def pack(data, pack_name)
return nil unless pack?(data, pack_name)
pack_data = {
common: pack_name == 'common' ? nil : resolve_pack(data['name'] ? Themes.instance.get(current_theme) : Themes.instance.core, 'common'),
name: data['name'],
pack: pack_name,
preload: nil,
stylesheet: false
}
if data['pack'][pack_name].is_a?(Hash)
pack_data[:common] = nil if data['pack'][pack_name]['use_common'] == false
pack_data[:pack] = nil unless data['pack'][pack_name]['filename']
if data['pack'][pack_name]['preload']
pack_data[:preload] = [data['pack'][pack_name]['preload']] if data['pack'][pack_name]['preload'].is_a?(String)
pack_data[:preload] = data['pack'][pack_name]['preload'] if data['pack'][pack_name]['preload'].is_a?(Array)
end
pack_data[:stylesheet] = true if data['pack'][pack_name]['stylesheet']
end
pack_data
end
def pack?(data, pack_name)
if data['pack'].is_a?(Hash) && data['pack'].key?(pack_name)
return true if data['pack'][pack_name].is_a?(String) || data['pack'][pack_name].is_a?(Hash)
end
false
end
def nil_pack(data, pack_name)
{
common: pack_name == 'common' ? nil : resolve_pack(data['name'] ? Themes.instance.get(current_theme) : Themes.instance.core, 'common'),
name: data['name'],
pack: nil,
preload: nil,
stylesheet: false
}
end
def resolve_pack(data, pack_name)
result = pack(data, pack_name)
unless result
if data['name'] && data.key?('fallback')
if data['fallback'].nil?
return nil_pack(data, pack_name)
elsif data['fallback'].is_a?(String) && Themes.instance.get(data['fallback'])
return resolve_pack(Themes.instance.get(data['fallback']), pack_name)
elsif data['fallback'].is_a?(Array)
data['fallback'].each do |fallback|
return resolve_pack(Themes.instance.get(fallback), pack_name) if Themes.instance.get(fallback)
end
end
return nil_pack(data, pack_name)
end
return data.key?('name') && data['name'] != default_theme ? resolve_pack(Themes.instance.get(default_theme), pack_name) : nil_pack(data, pack_name)
end
result
end
def use_pack(pack_name)
@core = resolve_pack(Themes.instance.core, pack_name)
@theme = resolve_pack(Themes.instance.get(current_theme), pack_name)
end
protected protected
def forbidden def forbidden
@ -84,13 +145,13 @@ class ApplicationController < ActionController::Base
@current_session ||= SessionActivation.find_by(session_id: cookies.signed['_session_id']) @current_session ||= SessionActivation.find_by(session_id: cookies.signed['_session_id'])
end end
def current_theme def default_theme
return Setting.default_settings['theme'] unless Themes.instance.names.include? current_user&.setting_theme Setting.default_settings['theme']
current_user.setting_theme
end end
def theme_data def current_theme
Themes.instance.get(current_theme) return default_theme unless Themes.instance.names.include? current_user&.setting_theme
current_user.setting_theme
end end
def cache_collection(raw, klass) def cache_collection(raw, klass)

View File

@ -2,10 +2,17 @@
class Auth::ConfirmationsController < Devise::ConfirmationsController class Auth::ConfirmationsController < Devise::ConfirmationsController
layout 'auth' layout 'auth'
before_action :set_pack
def show def show
super do |user| super do |user|
BootstrapTimelineWorker.perform_async(user.account_id) if user.errors.empty? BootstrapTimelineWorker.perform_async(user.account_id) if user.errors.empty?
end end
end end
private
def set_pack
use_pack 'auth'
end
end end

View File

@ -2,6 +2,7 @@
class Auth::PasswordsController < Devise::PasswordsController class Auth::PasswordsController < Devise::PasswordsController
before_action :check_validity_of_reset_password_token, only: :edit before_action :check_validity_of_reset_password_token, only: :edit
before_action :set_pack
layout 'auth' layout 'auth'
@ -17,4 +18,8 @@ class Auth::PasswordsController < Devise::PasswordsController
def reset_password_token_is_valid? def reset_password_token_is_valid?
resource_class.with_reset_password_token(params[:reset_password_token]).present? resource_class.with_reset_password_token(params[:reset_password_token]).present?
end end
def set_pack
use_pack 'auth'
end
end end

View File

@ -5,6 +5,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
before_action :check_enabled_registrations, only: [:new, :create] before_action :check_enabled_registrations, only: [:new, :create]
before_action :configure_sign_up_params, only: [:create] before_action :configure_sign_up_params, only: [:create]
before_action :set_path
before_action :set_sessions, only: [:edit, :update] before_action :set_sessions, only: [:edit, :update]
before_action :set_instance_presenter, only: [:new, :create, :update] before_action :set_instance_presenter, only: [:new, :create, :update]
@ -40,6 +41,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
private private
def set_pack
use_pack %w(edit update).include?(action_name) ? 'admin' : 'auth'
end
def set_instance_presenter def set_instance_presenter
@instance_presenter = InstancePresenter.new @instance_presenter = InstancePresenter.new
end end

View File

@ -9,6 +9,7 @@ class Auth::SessionsController < Devise::SessionsController
skip_before_action :check_suspension, only: [:destroy] skip_before_action :check_suspension, only: [:destroy]
prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create] prepend_before_action :authenticate_with_two_factor, if: :two_factor_enabled?, only: [:create]
before_action :set_instance_presenter, only: [:new] before_action :set_instance_presenter, only: [:new]
before_action :set_pack
def create def create
super do |resource| super do |resource|
@ -85,6 +86,10 @@ class Auth::SessionsController < Devise::SessionsController
private private
def set_pack
use_pack 'auth'
end
def set_instance_presenter def set_instance_presenter
@instance_presenter = InstancePresenter.new @instance_presenter = InstancePresenter.new
end end

View File

@ -4,6 +4,7 @@ class AuthorizeFollowsController < ApplicationController
layout 'modal' layout 'modal'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_pack
def show def show
@account = located_account || render(:error) @account = located_account || render(:error)
@ -23,6 +24,10 @@ class AuthorizeFollowsController < ApplicationController
private private
def set_pack
use_pack 'modal'
end
def follow_attempt def follow_attempt
FollowService.new.call(current_account, acct_without_prefix) FollowService.new.call(current_account, acct_without_prefix)
end end

View File

@ -7,7 +7,9 @@ class FollowerAccountsController < ApplicationController
@follows = Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account) @follows = Follow.where(target_account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:account)
respond_to do |format| respond_to do |format|
format.html format.html do
use_pack 'public'
end
format.json do format.json do
render json: collection_presenter, render json: collection_presenter,

View File

@ -7,7 +7,9 @@ class FollowingAccountsController < ApplicationController
@follows = Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account) @follows = Follow.where(account: @account).recent.page(params[:page]).per(FOLLOW_PER_PAGE).preload(:target_account)
respond_to do |format| respond_to do |format|
format.html format.html do
use_pack 'public'
end
format.json do format.json do
render json: collection_presenter, render json: collection_presenter,

View File

@ -2,6 +2,7 @@
class HomeController < ApplicationController class HomeController < ApplicationController
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_pack
before_action :set_initial_state_json before_action :set_initial_state_json
def index def index
@ -37,6 +38,10 @@ class HomeController < ApplicationController
redirect_to(default_redirect_path) redirect_to(default_redirect_path)
end end
def set_pack
use_pack 'home'
end
def set_initial_state_json def set_initial_state_json
serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer) serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
@initial_state_json = serializable_resource.to_json @initial_state_json = serializable_resource.to_json

View File

@ -4,6 +4,7 @@ class RemoteFollowController < ApplicationController
layout 'modal' layout 'modal'
before_action :set_account before_action :set_account
before_action :set_pack
before_action :gone, if: :suspended_account? before_action :gone, if: :suspended_account?
def new def new
@ -31,6 +32,10 @@ class RemoteFollowController < ApplicationController
{ acct: session[:remote_follow] } { acct: session[:remote_follow] }
end end
def set_pack
use_pack 'modal'
end
def set_account def set_account
@account = Account.find_local!(params[:account_username]) @account = Account.find_local!(params[:account_username])
end end

View File

@ -1,9 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ApplicationsController < ApplicationController class Settings::ApplicationsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_application, only: [:show, :update, :destroy, :regenerate] before_action :set_application, only: [:show, :update, :destroy, :regenerate]
before_action :prepare_scopes, only: [:create, :update] before_action :prepare_scopes, only: [:create, :update]

View File

@ -0,0 +1,12 @@
# frozen_string_literal: true
class Settings::BaseController < ApplicationController
layout 'admin'
before_action :authenticate_user!
before_action :set_pack
def set_pack
use_pack 'settings'
end
end

View File

@ -1,10 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::DeletesController < ApplicationController class Settings::DeletesController < Settings::BaseController
layout 'admin'
before_action :check_enabled_deletion before_action :check_enabled_deletion
before_action :authenticate_user!
def show def show
@confirmation = Form::DeleteConfirmation.new @confirmation = Form::DeleteConfirmation.new

View File

@ -1,10 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ExportsController < ApplicationController class Settings::ExportsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
def show def show
@export = Export.new(current_account) @export = Export.new(current_account)
end end

View File

@ -2,11 +2,7 @@
require 'sidekiq-bulk' require 'sidekiq-bulk'
class Settings::FollowerDomainsController < ApplicationController class Settings::FollowerDomainsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
def show def show
@account = current_account @account = current_account
@domains = current_account.followers.reorder('MIN(follows.id) DESC').group('accounts.domain').select('accounts.domain, count(accounts.id) as accounts_from_domain').page(params[:page]).per(10) @domains = current_account.followers.reorder('MIN(follows.id) DESC').group('accounts.domain').select('accounts.domain, count(accounts.id) as accounts_from_domain').page(params[:page]).per(10)

View File

@ -1,9 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ImportsController < ApplicationController class Settings::ImportsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :set_account before_action :set_account
def show def show

View File

@ -1,9 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::KeywordMutesController < ApplicationController class Settings::KeywordMutesController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
before_action :load_keyword_mute, only: [:edit, :update, :destroy] before_action :load_keyword_mute, only: [:edit, :update, :destroy]
def index def index

View File

@ -1,10 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::NotificationsController < ApplicationController class Settings::NotificationsController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
def show; end def show; end
def update def update

View File

@ -1,10 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::PreferencesController < ApplicationController class Settings::PreferencesController < Settings::BaseController
layout 'admin'
before_action :authenticate_user!
def show; end def show; end
def update def update

View File

@ -1,11 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class Settings::ProfilesController < ApplicationController class Settings::ProfilesController < Settings::BaseController
include ObfuscateFilename include ObfuscateFilename
layout 'admin'
before_action :authenticate_user!
before_action :set_account before_action :set_account
obfuscate_filename [:account, :avatar] obfuscate_filename [:account, :avatar]

View File

@ -1,5 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
# Intentionally does not inherit from BaseController
class Settings::SessionsController < ApplicationController class Settings::SessionsController < ApplicationController
before_action :set_session, only: :destroy before_action :set_session, only: :destroy

View File

@ -1,10 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
module Settings module Settings
class TwoFactorAuthenticationsController < ApplicationController class TwoFactorAuthenticationsController < BaseController
layout 'admin'
before_action :authenticate_user!
before_action :verify_otp_required, only: [:create] before_action :verify_otp_required, only: [:create]
def show def show

View File

@ -4,6 +4,7 @@ class SharesController < ApplicationController
layout 'modal' layout 'modal'
before_action :authenticate_user! before_action :authenticate_user!
before_action :set_pack
before_action :set_body_classes before_action :set_body_classes
def show def show
@ -24,6 +25,10 @@ class SharesController < ApplicationController
} }
end end
def set_pack
use_pack 'share'
end
def set_body_classes def set_body_classes
@body_classes = 'compose-standalone' @body_classes = 'compose-standalone'
end end

View File

@ -14,6 +14,7 @@ class StatusesController < ApplicationController
def show def show
respond_to do |format| respond_to do |format|
format.html do format.html do
use_pack 'public'
@ancestors = @status.reply? ? cache_collection(@status.ancestors(current_account), Status) : [] @ancestors = @status.reply? ? cache_collection(@status.ancestors(current_account), Status) : []
@descendants = cache_collection(@status.descendants(current_account), Status) @descendants = cache_collection(@status.descendants(current_account), Status)
@ -37,6 +38,7 @@ class StatusesController < ApplicationController
end end
def embed def embed
use_pack 'embed'
response.headers['X-Frame-Options'] = 'ALLOWALL' response.headers['X-Frame-Options'] = 'ALLOWALL'
render 'stream_entries/embed', layout: 'embedded' render 'stream_entries/embed', layout: 'embedded'
end end

View File

@ -14,6 +14,7 @@ class StreamEntriesController < ApplicationController
def show def show
respond_to do |format| respond_to do |format|
format.html do format.html do
use_pack 'public'
@ancestors = @stream_entry.activity.reply? ? cache_collection(@stream_entry.activity.ancestors(current_account), Status) : [] @ancestors = @stream_entry.activity.reply? ? cache_collection(@stream_entry.activity.ancestors(current_account), Status) : []
@descendants = cache_collection(@stream_entry.activity.descendants(current_account), Status) @descendants = cache_collection(@stream_entry.activity.descendants(current_account), Status)
end end

View File

@ -9,6 +9,7 @@ class TagsController < ApplicationController
respond_to do |format| respond_to do |format|
format.html do format.html do
use_pack 'about'
serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer) serializable_resource = ActiveModelSerializers::SerializableResource.new(InitialStatePresenter.new(initial_state_params), serializer: InitialStateSerializer)
@initial_state_json = serializable_resource.to_json @initial_state_json = serializable_resource.to_json
end end

View File

@ -1 +0,0 @@
// This file will be loaded on about pages, regardless of theme.

View File

@ -13,7 +13,7 @@ window.addEventListener('message', e => {
id: data.id, id: data.id,
height: document.getElementsByTagName('html')[0].scrollHeight, height: document.getElementsByTagName('html')[0].scrollHeight,
}, '*'); }, '*');
}); };
if (['interactive', 'complete'].includes(document.readyState)) { if (['interactive', 'complete'].includes(document.readyState)) {
setEmbedHeight(); setEmbedHeight();

View File

@ -1 +0,0 @@
// This file will be loaded on home pages, regardless of theme.

View File

@ -1 +1,25 @@
// This file will be loaded on public pages, regardless of theme. // This file will be loaded on public pages, regardless of theme.
const { delegate } = require('rails-ujs');
delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
if (button !== 0) {
return true;
}
window.location.href = target.href;
return false;
});
delegate(document, '.status__content__spoiler-link', 'click', ({ target }) => {
const contentEl = target.parentNode.parentNode.querySelector('.e-content');
if (contentEl.style.display === 'block') {
contentEl.style.display = 'none';
target.parentNode.style.marginBottom = 0;
} else {
contentEl.style.display = 'block';
target.parentNode.style.marginBottom = null;
}
return false;
});

View File

@ -1,65 +1,37 @@
// This file will be loaded on settings pages, regardless of theme. // This file will be loaded on settings pages, regardless of theme.
function main() { const { length } = require('stringz');
const { length } = require('stringz'); const { delegate } = require('rails-ujs');
const { delegate } = require('rails-ujs');
delegate(document, '.webapp-btn', 'click', ({ target, button }) => { delegate(document, '.account_display_name', 'input', ({ target }) => {
if (button !== 0) { const nameCounter = document.querySelector('.name-counter');
return true;
}
window.location.href = target.href;
return false;
});
delegate(document, '.status__content__spoiler-link', 'click', ({ target }) => { if (nameCounter) {
const contentEl = target.parentNode.parentNode.querySelector('.e-content'); nameCounter.textContent = 30 - length(target.value);
}
if (contentEl.style.display === 'block') { });
contentEl.style.display = 'none';
target.parentNode.style.marginBottom = 0; delegate(document, '.account_note', 'input', ({ target }) => {
} else { const noteCounter = document.querySelector('.note-counter');
contentEl.style.display = 'block';
target.parentNode.style.marginBottom = null; if (noteCounter) {
} const noteWithoutMetadata = processBio(target.value).text;
noteCounter.textContent = 500 - length(noteWithoutMetadata);
return false; }
}); });
delegate(document, '.account_display_name', 'input', ({ target }) => { delegate(document, '#account_avatar', 'change', ({ target }) => {
const nameCounter = document.querySelector('.name-counter'); const avatar = document.querySelector('.card.compact .avatar img');
const [file] = target.files || [];
if (nameCounter) { const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
nameCounter.textContent = 30 - length(target.value);
} avatar.src = url;
}); });
delegate(document, '.account_note', 'input', ({ target }) => { delegate(document, '#account_header', 'change', ({ target }) => {
const noteCounter = document.querySelector('.note-counter'); const header = document.querySelector('.card.compact');
const [file] = target.files || [];
if (noteCounter) { const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc;
const noteWithoutMetadata = processBio(target.value).text;
noteCounter.textContent = 500 - length(noteWithoutMetadata); header.style.backgroundImage = `url(${url})`;
}
});
delegate(document, '#account_avatar', 'change', ({ target }) => {
const avatar = document.querySelector('.card.compact .avatar img');
const [file] = target.files || [];
const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
avatar.src = url;
});
delegate(document, '#account_header', 'change', ({ target }) => {
const header = document.querySelector('.card.compact');
const [file] = target.files || [];
const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc;
header.style.backgroundImage = `url(${url})`;
});
}
loadPolyfills().then(main).catch(error => {
console.error(error);
}); });

View File

@ -1 +0,0 @@
// This file will be loaded on share pages, regardless of theme.

View File

@ -0,0 +1,14 @@
# These packs will be loaded on every appropriate page, regardless of
# theme.
pack:
about:
admin: admin.js
auth:
common: common.js
embed: embed.js
error:
home:
modal:
public: public.js
settings: settings.js
share:

View File

@ -0,0 +1,9 @@
let theLocale;
export function setLocale(locale) {
theLocale = locale;
}
export function getLocale() {
return theLocale;
}

View File

@ -1,9 +1 @@
let theLocale; export * from 'locales';
export function setLocale(locale) {
theLocale = locale;
}
export function getLocale() {
return theLocale;
}

View File

@ -0,0 +1,22 @@
import loadPolyfills from '../mastodon/load_polyfills';
function loaded() {
const TimelineContainer = require('../mastodon/containers/timeline_container').default;
const React = require('react');
const ReactDOM = require('react-dom');
const mountNode = document.getElementById('mastodon-timeline');
if (mountNode !== null) {
const props = JSON.parse(mountNode.getAttribute('data-props'));
ReactDOM.render(<TimelineContainer {...props} />, mountNode);
}
}
function main() {
const ready = require('../mastodon/ready').default;
ready(loaded);
}
loadPolyfills().then(main).catch(error => {
console.error(error);
});

View File

@ -1,15 +1,5 @@
// THIS IS THE `vanilla` THEME PACK FILE!!
// IT'S HERE FOR UPSTREAM COMPATIBILITY!!
// THE `glitch` PACK FILE IS IN `themes/glitch/index.js`!!
import loadPolyfills from '../mastodon/load_polyfills'; import loadPolyfills from '../mastodon/load_polyfills';
// import default stylesheet with variables
import 'font-awesome/css/font-awesome.css';
import '../styles/application.scss';
require.context('../images/', true);
loadPolyfills().then(() => { loadPolyfills().then(() => {
require('../mastodon/main').default(); require('../mastodon/main').default();
}).catch(e => { }).catch(e => {

View File

@ -0,0 +1,3 @@
import 'font-awesome/css/font-awesome.css';
import 'styles/application.scss'
require.context('../images/', true);

View File

@ -0,0 +1,75 @@
import loadPolyfills from '../mastodon/load_polyfills';
import ready from '../mastodon/ready';
function main() {
const IntlRelativeFormat = require('intl-relativeformat').default;
const emojify = require('../mastodon/features/emoji/emoji').default;
const { getLocale } = require('../mastodon/locales');
const { localeData } = getLocale();
const VideoContainer = require('../mastodon/containers/video_container').default;
const MediaGalleryContainer = require('../mastodon/containers/media_gallery_container').default;
const CardContainer = require('../mastodon/containers/card_container').default;
const React = require('react');
const ReactDOM = require('react-dom');
localeData.forEach(IntlRelativeFormat.__addLocaleData);
ready(() => {
const locale = document.documentElement.lang;
const dateTimeFormat = new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'long',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
});
const relativeFormat = new IntlRelativeFormat(locale);
[].forEach.call(document.querySelectorAll('.emojify'), (content) => {
content.innerHTML = emojify(content.innerHTML);
});
[].forEach.call(document.querySelectorAll('time.formatted'), (content) => {
const datetime = new Date(content.getAttribute('datetime'));
const formattedDate = dateTimeFormat.format(datetime);
content.title = formattedDate;
content.textContent = formattedDate;
});
[].forEach.call(document.querySelectorAll('time.time-ago'), (content) => {
const datetime = new Date(content.getAttribute('datetime'));
content.title = dateTimeFormat.format(datetime);
content.textContent = relativeFormat.format(datetime);
});
[].forEach.call(document.querySelectorAll('.logo-button'), (content) => {
content.addEventListener('click', (e) => {
e.preventDefault();
window.open(e.target.href, 'mastodon-intent', 'width=400,height=400,resizable=no,menubar=no,status=no,scrollbars=yes');
});
});
[].forEach.call(document.querySelectorAll('[data-component="Video"]'), (content) => {
const props = JSON.parse(content.getAttribute('data-props'));
ReactDOM.render(<VideoContainer locale={locale} {...props} />, content);
});
[].forEach.call(document.querySelectorAll('[data-component="MediaGallery"]'), (content) => {
const props = JSON.parse(content.getAttribute('data-props'));
ReactDOM.render(<MediaGalleryContainer locale={locale} {...props} />, content);
});
[].forEach.call(document.querySelectorAll('[data-component="Card"]'), (content) => {
const props = JSON.parse(content.getAttribute('data-props'));
ReactDOM.render(<CardContainer locale={locale} {...props} />, content);
});
});
}
loadPolyfills().then(main).catch(error => {
console.error(error);
});

View File

@ -0,0 +1,22 @@
import loadPolyfills from '../mastodon/load_polyfills';
function loaded() {
const ComposeContainer = require('../mastodon/containers/compose_container').default;
const React = require('react');
const ReactDOM = require('react-dom');
const mountNode = document.getElementById('mastodon-compose');
if (mountNode !== null) {
const props = JSON.parse(mountNode.getAttribute('data-props'));
ReactDOM.render(<ComposeContainer {...props} />, mountNode);
}
}
function main() {
const ready = require('../mastodon/ready').default;
ready(loaded);
}
loadPolyfills().then(main).catch(error => {
console.error(error);
});

View File

@ -1,5 +0,0 @@
// This makes our fonts available everywhere.
@import 'fonts/roboto';
@import 'fonts/roboto-mono';
@import 'fonts/montserrat';

View File

@ -1,3 +1,8 @@
// win95 theme from cybrespace.
// Modified to inherit glitch styles (themes/glitch/styles/index.scss)
// instead of vanilla ones (./application.scss)
$win95-bg: #bfbfbf; $win95-bg: #bfbfbf;
$win95-dark-grey: #404040; $win95-dark-grey: #404040;
$win95-mid-grey: #808080; $win95-mid-grey: #808080;
@ -17,7 +22,7 @@ $ui-highlight-color: $win95-window-header;
} }
@mixin win95-outset() { @mixin win95-outset() {
box-shadow: inset -1px -1px 0px #000000, box-shadow: inset -1px -1px 0px #000000,
inset 1px 1px 0px #ffffff, inset 1px 1px 0px #ffffff,
inset -2px -2px 0px #808080, inset -2px -2px 0px #808080,
inset 2px 2px 0px #dfdfdf; inset 2px 2px 0px #dfdfdf;
@ -41,7 +46,7 @@ $ui-highlight-color: $win95-window-header;
} }
@mixin win95-inset() { @mixin win95-inset() {
box-shadow: inset 1px 1px 0px #000000, box-shadow: inset 1px 1px 0px #000000,
inset -1px -1px 0px #ffffff, inset -1px -1px 0px #ffffff,
inset 2px 2px 0px #808080, inset 2px 2px 0px #808080,
inset -2px -2px 0px #dfdfdf; inset -2px -2px 0px #dfdfdf;
@ -51,7 +56,7 @@ $ui-highlight-color: $win95-window-header;
@mixin win95-tab() { @mixin win95-tab() {
box-shadow: inset -1px 0px 0px #000000, box-shadow: inset -1px 0px 0px #000000,
inset 1px 0px 0px #ffffff, inset 1px 0px 0px #ffffff,
inset 0px 1px 0px #ffffff, inset 0px 1px 0px #ffffff,
inset 0px 2px 0px #dfdfdf, inset 0px 2px 0px #dfdfdf,
@ -71,7 +76,7 @@ $ui-highlight-color: $win95-window-header;
src: url('../fonts/premillenium/MSSansSerif.ttf') format('truetype'); src: url('../fonts/premillenium/MSSansSerif.ttf') format('truetype');
} }
@import 'application'; @import '../themes/glitch/styles/index'; // Imports glitch themes
/* borrowed from cybrespace style: wider columns and full column width images */ /* borrowed from cybrespace style: wider columns and full column width images */
@ -174,7 +179,7 @@ body.admin {
font-size:0px; font-size:0px;
color:$win95-bg; color:$win95-bg;
background-image: url("../images/start.png"); background-image: url("../images/start.png");
background-repeat:no-repeat; background-repeat:no-repeat;
background-position:8%; background-position:8%;
background-clip:padding-box; background-clip:padding-box;
@ -336,7 +341,7 @@ body.admin {
border-radius:0px; border-radius:0px;
background-color:white; background-color:white;
@include win95-border-inset(); @include win95-border-inset();
width:12px; width:12px;
height:12px; height:12px;
} }
@ -515,9 +520,9 @@ body.admin {
color:black; color:black;
font-weight:bold; font-weight:bold;
} }
.account__avatar, .account__avatar,
.account__avatar-overlay-base, .account__avatar-overlay-base,
.account__header__avatar, .account__header__avatar,
.account__avatar-overlay-overlay { .account__avatar-overlay-overlay {
@include win95-border-slight-inset(); @include win95-border-slight-inset();
clip-path:none; clip-path:none;
@ -627,7 +632,7 @@ body.admin {
} }
.status-card__description { .status-card__description {
color:black; color:black;
} }
.account__display-name strong, .status__display-name strong { .account__display-name strong, .status__display-name strong {
@ -710,8 +715,8 @@ body.admin {
width:40px; width:40px;
font-size:0px; font-size:0px;
color:$win95-bg; color:$win95-bg;
background-image: url("../images/start.png"); background-image: url("../images/start.png");
background-repeat:no-repeat; background-repeat:no-repeat;
background-position:8%; background-position:8%;
background-clip:padding-box; background-clip:padding-box;
@ -723,7 +728,7 @@ body.admin {
} }
.drawer__header a:first-child:hover { .drawer__header a:first-child:hover {
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAIAAACpTQvdAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAF3pUWHRBdXRob3IAAAiZS84oys9LzAEAC5oC6A7BY/IAAACWSURBVCiRhVJJDsQgDEuqOfRZ7a1P5gbP4uaJaEjTADMWQhHYjlk4p0wLnNdptdF4KvBUDyGzVwc2xO+uKtH+1o0ytEEmqFpuxlvFCGCxKbNIT56QCi2MzaA/2Mz+mERSOeqzJG2RUxkjdTabgPtFoZ1bZxcKvgPcLZVufAyR9Ni8v5dWDzfFx0giC1RvZFv6l35QQ/Mvv39XXgGzQpoAAAAASUVORK5CYII="); background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAIAAACpTQvdAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAF3pUWHRBdXRob3IAAAiZS84oys9LzAEAC5oC6A7BY/IAAACWSURBVCiRhVJJDsQgDEuqOfRZ7a1P5gbP4uaJaEjTADMWQhHYjlk4p0wLnNdptdF4KvBUDyGzVwc2xO+uKtH+1o0ytEEmqFpuxlvFCGCxKbNIT56QCi2MzaA/2Mz+mERSOeqzJG2RUxkjdTabgPtFoZ1bZxcKvgPcLZVufAyR9Ni8v5dWDzfFx0giC1RvZFv6l35QQ/Mvv39XXgGzQpoAAAAASUVORK5CYII=");
background-repeat:no-repeat; background-repeat:no-repeat;
background-position:8%; background-position:8%;
background-clip:padding-box; background-clip:padding-box;
@ -732,7 +737,7 @@ body.admin {
} }
.drawer__tab:first-child { .drawer__tab:first-child {
} }
.search { .search {
@ -844,7 +849,7 @@ body.admin {
padding:4px 8px; padding:4px 8px;
} }
.privacy-dropdown.active .privacy-dropdown.active
.privacy-dropdown__value { .privacy-dropdown__value {
background: $win95-bg; background: $win95-bg;
box-shadow:unset; box-shadow:unset;
@ -935,7 +940,7 @@ body.admin {
background-color:$win95-bg; background-color:$win95-bg;
border:1px solid black; border:1px solid black;
box-sizing:content-box; box-sizing:content-box;
} }
.emoji-dialog .emoji-search { .emoji-dialog .emoji-search {
@ -1010,8 +1015,8 @@ body.admin {
width:60px; width:60px;
font-size:0px; font-size:0px;
color:$win95-bg; color:$win95-bg;
background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAIAAACpTQvdAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAF3pUWHRBdXRob3IAAAiZS84oys9LzAEAC5oC6A7BY/IAAACWSURBVCiRhVJJDsQgDEuqOfRZ7a1P5gbP4uaJaEjTADMWQhHYjlk4p0wLnNdptdF4KvBUDyGzVwc2xO+uKtH+1o0ytEEmqFpuxlvFCGCxKbNIT56QCi2MzaA/2Mz+mERSOeqzJG2RUxkjdTabgPtFoZ1bZxcKvgPcLZVufAyR9Ni8v5dWDzfFx0giC1RvZFv6l35QQ/Mvv39XXgGzQpoAAAAASUVORK5CYII="); background-image: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAOCAIAAACpTQvdAAAACXBIWXMAAC4jAAAuIwF4pT92AAAAF3pUWHRBdXRob3IAAAiZS84oys9LzAEAC5oC6A7BY/IAAACWSURBVCiRhVJJDsQgDEuqOfRZ7a1P5gbP4uaJaEjTADMWQhHYjlk4p0wLnNdptdF4KvBUDyGzVwc2xO+uKtH+1o0ytEEmqFpuxlvFCGCxKbNIT56QCi2MzaA/2Mz+mERSOeqzJG2RUxkjdTabgPtFoZ1bZxcKvgPcLZVufAyR9Ni8v5dWDzfFx0giC1RvZFv6l35QQ/Mvv39XXgGzQpoAAAAASUVORK5CYII=");
background-repeat:no-repeat; background-repeat:no-repeat;
background-position:8%; background-position:8%;
background-clip:padding-box; background-clip:padding-box;
@ -1049,40 +1054,40 @@ body.admin {
} }
} }
.column-link[href="/web/timelines/public"] { .column-link[href="/web/timelines/public"] {
background-image: url("../images/icon_public.png"); background-image: url("../images/icon_public.png");
&:hover { background-image: url("../images/icon_public.png"); } &:hover { background-image: url("../images/icon_public.png"); }
} }
.column-link[href="/web/timelines/public/local"] { .column-link[href="/web/timelines/public/local"] {
background-image: url("../images/icon_local.png"); background-image: url("../images/icon_local.png");
&:hover { background-image: url("../images/icon_local.png"); } &:hover { background-image: url("../images/icon_local.png"); }
} }
.column-link[href="/web/pinned"] { .column-link[href="/web/pinned"] {
background-image: url("../images/icon_pin.png"); background-image: url("../images/icon_pin.png");
&:hover { background-image: url("../images/icon_pin.png"); } &:hover { background-image: url("../images/icon_pin.png"); }
} }
.column-link[href="/web/favourites"] { .column-link[href="/web/favourites"] {
background-image: url("../images/icon_likes.png"); background-image: url("../images/icon_likes.png");
&:hover { background-image: url("../images/icon_likes.png"); } &:hover { background-image: url("../images/icon_likes.png"); }
} }
.column-link[href="/web/blocks"] { .column-link[href="/web/blocks"] {
background-image: url("../images/icon_blocks.png"); background-image: url("../images/icon_blocks.png");
&:hover { background-image: url("../images/icon_blocks.png"); } &:hover { background-image: url("../images/icon_blocks.png"); }
} }
.column-link[href="/web/mutes"] { .column-link[href="/web/mutes"] {
background-image: url("../images/icon_mutes.png"); background-image: url("../images/icon_mutes.png");
&:hover { background-image: url("../images/icon_mutes.png"); } &:hover { background-image: url("../images/icon_mutes.png"); }
} }
.column-link[href="/settings/preferences"] { .column-link[href="/settings/preferences"] {
background-image: url("../images/icon_settings.png"); background-image: url("../images/icon_settings.png");
&:hover { background-image: url("../images/icon_settings.png"); } &:hover { background-image: url("../images/icon_settings.png"); }
} }
.column-link[href="/about/more"] { .column-link[href="/about/more"] {
background-image: url("../images/icon_about.png"); background-image: url("../images/icon_about.png");
&:hover { background-image: url("../images/icon_about.png"); } &:hover { background-image: url("../images/icon_about.png"); }
} }
.column-link[href="/auth/sign_out"] { .column-link[href="/auth/sign_out"] {
background-image: url("../images/icon_logout.png"); background-image: url("../images/icon_logout.png");
&:hover { background-image: url("../images/icon_logout.png"); } &:hover { background-image: url("../images/icon_logout.png"); }
} }
@ -1098,7 +1103,7 @@ body.admin {
line-height:30px; line-height:30px;
padding-left:20px; padding-left:20px;
padding-right:40px; padding-right:40px;
left:0px; left:0px;
bottom:-30px; bottom:-30px;
display:block; display:block;
@ -1106,9 +1111,9 @@ body.admin {
background-color:#7f7f7f; background-color:#7f7f7f;
width:200%; width:200%;
height:30px; height:30px;
-ms-transform: rotate(-90deg); -ms-transform: rotate(-90deg);
-webkit-transform: rotate(-90deg); -webkit-transform: rotate(-90deg);
transform: rotate(-90deg); transform: rotate(-90deg);
transform-origin:top left; transform-origin:top left;
@ -1189,7 +1194,7 @@ body.admin {
left:unset; left:unset;
} }
.dropdown > .icon-button, .detailed-status__button > .icon-button, .dropdown > .icon-button, .detailed-status__button > .icon-button,
.status__action-bar > .icon-button, .star-icon i { .status__action-bar > .icon-button, .star-icon i {
/* i don't know what's going on with the inline /* i don't know what's going on with the inline
styles someone should look at the react code */ styles someone should look at the react code */
@ -1239,8 +1244,8 @@ body.admin {
background:$win95-bg; background:$win95-bg;
} }
.actions-modal::before, .actions-modal::before,
.boost-modal::before, .boost-modal::before,
.confirmation-modal::before, .confirmation-modal::before,
.report-modal::before { .report-modal::before {
content: "Confirmation"; content: "Confirmation";
@ -1278,8 +1283,8 @@ body.admin {
.confirmation-modal__cancel-button { .confirmation-modal__cancel-button {
color:black; color:black;
&:active, &:active,
&:focus, &:focus,
&:hover { &:hover {
color:black; color:black;
} }
@ -1566,10 +1571,10 @@ a.table-action-link:hover,
background-color:white; background-color:white;
} }
.simple_form input[type=text], .simple_form input[type=text],
.simple_form input[type=number], .simple_form input[type=number],
.simple_form input[type=email], .simple_form input[type=email],
.simple_form input[type=password], .simple_form input[type=password],
.simple_form textarea { .simple_form textarea {
color:black; color:black;
background-color:white; background-color:white;
@ -1580,8 +1585,8 @@ a.table-action-link:hover,
} }
} }
.simple_form button, .simple_form button,
.simple_form .button, .simple_form .button,
.simple_form .block-button .simple_form .block-button
{ {
background: $win95-bg; background: $win95-bg;
@ -1608,8 +1613,8 @@ a.table-action-link:hover,
} }
} }
.simple_form button.negative, .simple_form button.negative,
.simple_form .button.negative, .simple_form .button.negative,
.simple_form .block-button.negative .simple_form .block-button.negative
{ {
background: $win95-bg; background: $win95-bg;
@ -1631,8 +1636,8 @@ a.table-action-link:hover,
border-right-color:#f5f5f5; border-right-color:#f5f5f5;
width:12px; width:12px;
height:12px; height:12px;
display:inline-block; display:inline-block;
vertical-align:middle; vertical-align:middle;
margin-right:2px; margin-right:2px;
} }

View File

@ -9,7 +9,7 @@ import UI from 'themes/glitch/features/ui';
import { hydrateStore } from 'themes/glitch/actions/store'; import { hydrateStore } from 'themes/glitch/actions/store';
import { connectUserStream } from 'themes/glitch/actions/streaming'; import { connectUserStream } from 'themes/glitch/actions/streaming';
import { IntlProvider, addLocaleData } from 'react-intl'; import { IntlProvider, addLocaleData } from 'react-intl';
import { getLocale } from 'mastodon/locales'; import { getLocale } from 'locales';
import initialState from 'themes/glitch/util/initial_state'; import initialState from 'themes/glitch/util/initial_state';
const { localeData, messages } = getLocale(); const { localeData, messages } = getLocale();

View File

@ -1,3 +1,3 @@
import 'font-awesome/css/font-awesome.css'; import 'font-awesome/css/font-awesome.css';
require.context('../../images/', true); require.context('images/', true);
import './styles/index.scss'; import 'themes/glitch/styles/index.scss';

View File

@ -1,7 +1,7 @@
import loadPolyfills from './util/load_polyfills'; import loadPolyfills from 'themes/glitch/util/load_polyfills';
loadPolyfills().then(() => { loadPolyfills().then(() => {
require('./util/main').default(); require('themes/glitch/util/main').default();
}).catch(e => { }).catch(e => {
console.error(e); console.error(e);
}); });

View File

@ -2,32 +2,14 @@ import loadPolyfills from 'themes/glitch/util/load_polyfills';
import { processBio } from 'themes/glitch/util/bio_metadata'; import { processBio } from 'themes/glitch/util/bio_metadata';
import ready from 'themes/glitch/util/ready'; import ready from 'themes/glitch/util/ready';
window.addEventListener('message', e => {
const data = e.data || {};
if (!window.parent || data.type !== 'setHeight') {
return;
}
ready(() => {
window.parent.postMessage({
type: 'setHeight',
id: data.id,
height: document.getElementsByTagName('html')[0].scrollHeight,
}, '*');
});
});
function main() { function main() {
const { length } = require('stringz');
const IntlRelativeFormat = require('intl-relativeformat').default; const IntlRelativeFormat = require('intl-relativeformat').default;
const { delegate } = require('rails-ujs'); const emojify = require('themes/glitch/util/emoji').default;
const emojify = require('../themes/glitch/util/emoji').default; const { getLocale } = require('locales');
const { getLocale } = require('mastodon/locales');
const { localeData } = getLocale(); const { localeData } = getLocale();
const VideoContainer = require('../themes/glitch/containers/video_container').default; const VideoContainer = require('themes/glitch/containers/video_container').default;
const MediaGalleryContainer = require('../themes/glitch/containers/media_gallery_container').default; const MediaGalleryContainer = require('themes/glitch/containers/media_gallery_container').default;
const CardContainer = require('../themes/glitch/containers/card_container').default; const CardContainer = require('themes/glitch/containers/card_container').default;
const React = require('react'); const React = require('react');
const ReactDOM = require('react-dom'); const ReactDOM = require('react-dom');
@ -87,61 +69,6 @@ function main() {
ReactDOM.render(<CardContainer locale={locale} {...props} />, content); ReactDOM.render(<CardContainer locale={locale} {...props} />, content);
}); });
}); });
delegate(document, '.webapp-btn', 'click', ({ target, button }) => {
if (button !== 0) {
return true;
}
window.location.href = target.href;
return false;
});
delegate(document, '.status__content__spoiler-link', 'click', ({ target }) => {
const contentEl = target.parentNode.parentNode.querySelector('.e-content');
if (contentEl.style.display === 'block') {
contentEl.style.display = 'none';
target.parentNode.style.marginBottom = 0;
} else {
contentEl.style.display = 'block';
target.parentNode.style.marginBottom = null;
}
return false;
});
delegate(document, '.account_display_name', 'input', ({ target }) => {
const nameCounter = document.querySelector('.name-counter');
if (nameCounter) {
nameCounter.textContent = 30 - length(target.value);
}
});
delegate(document, '.account_note', 'input', ({ target }) => {
const noteCounter = document.querySelector('.note-counter');
if (noteCounter) {
const noteWithoutMetadata = processBio(target.value).text;
noteCounter.textContent = 500 - length(noteWithoutMetadata);
}
});
delegate(document, '#account_avatar', 'change', ({ target }) => {
const avatar = document.querySelector('.card.compact .avatar img');
const [file] = target.files || [];
const url = file ? URL.createObjectURL(file) : avatar.dataset.originalSrc;
avatar.src = url;
});
delegate(document, '#account_header', 'change', ({ target }) => {
const header = document.querySelector('.card.compact');
const [file] = target.files || [];
const url = file ? URL.createObjectURL(file) : header.dataset.originalSrc;
header.style.backgroundImage = `url(${url})`;
});
} }
loadPolyfills().then(main).catch(error => { loadPolyfills().then(main).catch(error => {

View File

@ -1,25 +1,34 @@
# (REQUIRED) The location of the pack files. # (REQUIRED) The location of the pack files.
pack: pack:
about: packs/about.js about: packs/about.js
admin: null admin:
common: packs/common.js auth:
embed: null common:
home: packs/home.js filename: packs/common.js
stylesheet: true
embed: packs/public.js
error:
home:
filename: packs/home.js
preload:
- themes/glitch/async/getting_started
- themes/glitch/async/compose
- themes/glitch/async/home_timeline
- themes/glitch/async/notifications
stylesheet: true
modal:
public: packs/public.js public: packs/public.js
settings: null settings:
share: packs/share.js share: packs/share.js
# (OPTIONAL) The directory which contains the pack files. # (OPTIONAL) The directory which contains the pack files.
# Defaults to the theme directory (`app/javascript/themes/[theme]`), # Defaults to the theme directory (`app/javascript/themes/[theme]`),
# which should be sufficient for like 99% of use-cases lol. # which should be sufficient for like 99% of use-cases lol.
# pack_directory: app/javascript/packs
# (OPTIONAL) Additional javascript resources to preload, for use with # pack_directory: app/javascript/packs
# lazy-loaded components. It is **STRONGLY RECOMMENDED** that you
# derive these pathnames from `themes/[your-theme]` to ensure that # (OPTIONAL) By default the theme will fallback to the default theme
# they stay unique. # if a particular pack is not provided. You can specify different
preload: # fallbacks here, or disable fallback behaviours altogether by
- themes/glitch/async/getting_started # specifying a `null` value.
- themes/glitch/async/compose fallback:
- themes/glitch/async/home_timeline
- themes/glitch/async/notifications

View File

@ -1,12 +1,23 @@
# (REQUIRED) The location of the pack files inside `pack_directory`. # (REQUIRED) The location of the pack files inside `pack_directory`.
pack: pack:
about: about.js about: about.js
admin: null admin:
common: common.js auth:
embed: null common:
home: application.js filename: common.js
stylesheet: true
embed: public.js
error:
home:
filename: application.js
preload:
- features/getting_started
- features/compose
- features/home_timeline
- features/notifications
modal:
public: public.js public: public.js
settings: null settings:
share: share.js share: share.js
# (OPTIONAL) The directory which contains the pack files. # (OPTIONAL) The directory which contains the pack files.
@ -15,12 +26,8 @@ pack:
# somewhere else. # somewhere else.
pack_directory: app/javascript/packs pack_directory: app/javascript/packs
# (OPTIONAL) Additional javascript resources to preload, for use with # (OPTIONAL) By default the theme will fallback to the default theme
# lazy-loaded components. It is **STRONGLY RECOMMENDED** that you # if a particular pack is not provided. You can specify different
# derive these pathnames from `themes/[your-theme]` to ensure that # fallbacks here, or disable fallback behaviours altogether by
# they stay unique. (Of course, vanilla doesn't do this ^^;;) # specifying a `null` value.
preload: fallback:
- features/getting_started
- features/compose
- features/home_timeline
- features/notifications

View File

@ -0,0 +1,10 @@
// These lines are the same as in glitch:
import 'font-awesome/css/font-awesome.css';
require.context('../../images/', true);
// …But we want to use our own styles instead.
import 'styles/win95.scss';
// Be sure to make this style file import from
// `themes/glitch/styles/index.scss` (the glitch styling), and not
// `application.scss` (which are the vanilla styles).

View File

@ -0,0 +1,23 @@
# win95 theme.
# Ported over from `cybrespace:mastodon/theme_win95`.
# <https://github.com/cybrespace/mastodon/tree/theme_win95>
# You can use this theme file as inspiration for porting over
# a preëxisting Mastodon theme.
# We only modify the `common` pack, which contains our styling.
pack:
common:
filename: index.js
stylesheet: true
# All unspecified packs will inherit from glitch.
# By default, the glitch preloads will also be used here. You can
# disable them by setting `preload` to `null`.
# preload:
# The `fallback` parameter tells us to use glitch files for everything
# we haven't specified.
fallback: glitch

View File

@ -7,15 +7,27 @@ class Themes
include Singleton include Singleton
def initialize def initialize
core = YAML.load_file(Rails.root.join('app', 'javascript', 'core', 'theme.yml'))
core['pack'] = Hash.new unless core['pack']
result = Hash.new result = Hash.new
Dir.glob(Rails.root.join('app', 'javascript', 'themes', '*', 'theme.yml')) do |path| Dir.glob(Rails.root.join('app', 'javascript', 'themes', '*', 'theme.yml')) do |path|
data = YAML.load_file(path) data = YAML.load_file(path)
name = File.basename(File.dirname(path)) name = File.basename(File.dirname(path))
if data['pack'] if data['pack']
data['name'] = name
result[name] = data result[name] = data
end end
end end
@core = core
@conf = result @conf = result
end
def core
@core
end end
def get(name) def get(name)

View File

@ -2,7 +2,6 @@
= site_hostname = site_hostname
- content_for :header_tags do - content_for :header_tags do
= javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
= render partial: 'shared/og' = render partial: 'shared/og'
.landing-page .landing-page

View File

@ -3,7 +3,6 @@
- content_for :header_tags do - content_for :header_tags do
%script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
= javascript_pack_tag 'about', integrity: true, crossorigin: 'anonymous'
= render partial: 'shared/og' = render partial: 'shared/og'
.landing-page .landing-page

View File

@ -1,6 +1,3 @@
- content_for :header_tags do
= javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
- content_for :page_title do - content_for :page_title do
= t('admin.reports.report', id: @report.id) = t('admin.reports.report', id: @report.id)

View File

@ -1,6 +1,3 @@
- content_for :header_tags do
= javascript_pack_tag 'admin', integrity: true, async: true, crossorigin: 'anonymous'
- content_for :page_title do - content_for :page_title do
= t('admin.statuses.title') = t('admin.statuses.title')

View File

@ -1,13 +1,7 @@
- content_for :header_tags do - content_for :header_tags do
- if theme_data['preload']
- theme_data['preload'].each do |link|
%link{ href: asset_pack_path("#{link}.js"), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/
%meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key} %meta{name: 'applicationServerKey', content: Rails.configuration.x.vapid_public_key}
%script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
= javascript_pack_tag "themes/#{current_theme}", integrity: true, crossorigin: 'anonymous'
= stylesheet_pack_tag "themes/#{current_theme}", integrity: true, media: 'all'
.app-holder#mastodon{ data: { props: Oj.dump(default_props) } } .app-holder#mastodon{ data: { props: Oj.dump(default_props) } }
%noscript %noscript
= image_tag asset_pack_path('logo.svg'), alt: 'Mastodon' = image_tag asset_pack_path('logo.svg'), alt: 'Mastodon'

View File

@ -0,0 +1,10 @@
- if theme
- if theme[:pack] != 'common' && theme[:common]
= render partial: 'layouts/theme', object: theme[:common]
- if theme[:pack]
= javascript_pack_tag theme[:name] ? "themes/#{theme[:name]}/#{theme[:pack]}" : "core/#{theme[:pack]}", integrity: true, crossorigin: 'anonymous'
- if theme[:stylesheet]
= stylesheet_pack_tag theme[:name] ? "themes/#{theme[:name]}/#{theme[:pack]}" : "core/#{theme[:pack]}", integrity: true, media: 'all'
- if theme[:preload]
- theme[:preload].each do |link|
%link{ href: asset_pack_path("#{link}.js"), crossorigin: 'anonymous', rel: 'preload', as: 'script' }/

View File

@ -1,6 +1,3 @@
- content_for :header_tags do
= javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
- content_for :content do - content_for :content do
.admin-wrapper .admin-wrapper
.sidebar-wrapper .sidebar-wrapper

View File

@ -18,16 +18,16 @@
= ' - ' = ' - '
= title = title
= stylesheet_pack_tag 'common', media: 'all' = javascript_pack_tag "locales", integrity: true, crossorigin: 'anonymous'
= javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous'
= javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous'
= csrf_meta_tags = csrf_meta_tags
- if controller_name != 'home'
= stylesheet_pack_tag 'application', integrity: true, media: 'all'
= yield :header_tags = yield :header_tags
-# These must come after :header_tags to ensure our initial state has been defined.
= render partial: 'layouts/theme', object: @core
= render partial: 'layouts/theme', object: @theme
- body_classes ||= @body_classes || '' - body_classes ||= @body_classes || ''
- body_classes += ' system-font' if current_account&.user&.setting_system_font_ui - body_classes += ' system-font' if current_account&.user&.setting_system_font_ui

View File

@ -1,6 +1,3 @@
- content_for :header_tags do
= javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
- content_for :content do - content_for :content do
.container .container
.logo-container .logo-container

View File

@ -4,10 +4,9 @@
%meta{ charset: 'utf-8' }/ %meta{ charset: 'utf-8' }/
%meta{ name: 'robots', content: 'noindex' }/ %meta{ name: 'robots', content: 'noindex' }/
= stylesheet_pack_tag 'common', media: 'all'
= javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous' = javascript_pack_tag 'common', integrity: true, crossorigin: 'anonymous'
= stylesheet_pack_tag 'application', integrity: true, media: 'all' = javascript_pack_tag 'embed', integrity: true, crossorigin: 'anonymous'
= javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous' = javascript_pack_tag "locale_#{I18n.locale}", integrity: true, crossorigin: 'anonymous'
= javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous' = render partial: 'layouts/theme', object: @core
%body.embed = render partial: 'layouts/theme', object: @theme
= yield = yield

View File

@ -5,8 +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 'common', media: 'all' = render partial: 'layouts/theme', object: @core
= stylesheet_pack_tag 'application', integrity: true, media: 'all' = render partial: 'layouts/theme', object: @theme
%body.error %body.error
.dialog .dialog
%img{ alt: Setting.default_settings['site_title'], src: '/oops.gif' }/ %img{ alt: Setting.default_settings['site_title'], src: '/oops.gif' }/

View File

@ -1,6 +1,3 @@
- content_for :header_tags do
= javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
- content_for :content do - content_for :content do
- if user_signed_in? - if user_signed_in?
.account-header .account-header

View File

@ -1,6 +1,3 @@
- content_for :header_tags do
= javascript_pack_tag 'public', integrity: true, crossorigin: 'anonymous'
- content_for :content do - content_for :content do
.container= yield .container= yield
.footer .footer

View File

@ -1,5 +1,4 @@
- content_for :header_tags do - content_for :header_tags do
%script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
= javascript_pack_tag 'share', integrity: true, crossorigin: 'anonymous'
#mastodon-compose{ data: { props: Oj.dump(default_props) } } #mastodon-compose{ data: { props: Oj.dump(default_props) } }

View File

@ -3,7 +3,6 @@
- content_for :header_tags do - content_for :header_tags do
%script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json) %script#initial-state{ type: 'application/json' }!= json_escape(@initial_state_json)
= javascript_pack_tag 'about', integrity: true, crossorigin: 'anonymous'
= render 'og' = render 'og'
.landing-page.tag-page .landing-page.tag-page

View File

@ -12,14 +12,24 @@ const settings = safeLoad(readFileSync(configPath), 'utf8')[env.NODE_ENV];
const themeFiles = glob.sync('app/javascript/themes/*/theme.yml'); const themeFiles = glob.sync('app/javascript/themes/*/theme.yml');
const themes = {}; const themes = {};
const core = function () {
const coreFile = resolve('app', 'javascript', 'core', 'theme.yml');
const data = safeLoad(readFileSync(coreFile), 'utf8');
if (!data.pack_directory) {
data.pack_directory = dirname(coreFile);
}
return data.pack ? data : {};
}();
for (let i = 0; i < themeFiles.length; i++) { for (let i = 0; i < themeFiles.length; i++) {
const themeFile = themeFiles[i]; const themeFile = themeFiles[i];
const data = safeLoad(readFileSync(themeFile), 'utf8'); const data = safeLoad(readFileSync(themeFile), 'utf8');
data.name = basename(dirname(themeFile));
if (!data.pack_directory) { if (!data.pack_directory) {
data.pack_directory = dirname(themeFile); data.pack_directory = dirname(themeFile);
} }
if (data.pack) { if (data.pack) {
themes[basename(dirname(themeFile))] = data; themes[data.name] = data;
} }
} }
@ -43,6 +53,7 @@ const output = {
module.exports = { module.exports = {
settings, settings,
core,
themes, themes,
env, env,
loadersDir, loadersDir,

View File

@ -57,7 +57,7 @@ Object.keys(glitchMessages).forEach(function (key) {
// //
import messages from '../../app/javascript/mastodon/locales/${locale}.json'; import messages from '../../app/javascript/mastodon/locales/${locale}.json';
import localeData from ${JSON.stringify(localeDataPath)}; import localeData from ${JSON.stringify(localeDataPath)};
import { setLocale } from '../../app/javascript/mastodon/locales'; import { setLocale } from 'locales';
${glitchInject} ${glitchInject}
setLocale({messages: mergedMessages, localeData: localeData}); setLocale({messages: mergedMessages, localeData: localeData});
`; `;

View File

@ -6,33 +6,37 @@ const { sync } = require('glob');
const ExtractTextPlugin = require('extract-text-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin');
const ManifestPlugin = require('webpack-manifest-plugin'); const ManifestPlugin = require('webpack-manifest-plugin');
const extname = require('path-complete-extname'); const extname = require('path-complete-extname');
const { env, settings, themes, output, loadersDir } = require('./configuration.js'); const { env, settings, core, themes, output, loadersDir } = require('./configuration.js');
const localePackPaths = require('./generateLocalePacks'); const localePackPaths = require('./generateLocalePacks');
const extensionGlob = `**/*{${settings.extensions.join(',')}}*`; function reducePacks (data, into = {}) {
const entryPath = join(settings.source_path, settings.source_entry_path); if (!data.pack) {
const packPaths = sync(join(entryPath, extensionGlob)); return into;
}
Object.keys(data.pack).reduce((map, entry) => {
const pack = data.pack[entry];
if (!pack) {
return map;
}
const packFile = typeof pack === 'string' ? pack : pack.filename;
if (packFile) {
map[data.name ? `themes/${data.name}/${entry}` : `core/${entry}`] = resolve(data.pack_directory, packFile);
}
return map;
}, into);
return into;
}
module.exports = { module.exports = {
entry: Object.assign( entry: Object.assign(
packPaths.reduce((map, entry) => { { locales: resolve('app', 'javascript', 'locales') },
const localMap = map;
const namespace = relative(join(entryPath), dirname(entry));
localMap[join(namespace, basename(entry, extname(entry)))] = resolve(entry);
return localMap;
}, {}),
localePackPaths.reduce((map, entry) => { localePackPaths.reduce((map, entry) => {
const localMap = map; const localMap = map;
localMap[basename(entry, extname(entry, extname(entry)))] = resolve(entry); localMap[basename(entry, extname(entry, extname(entry)))] = resolve(entry);
return localMap; return localMap;
}, {}), }, {}),
Object.keys(themes).reduce( reducePacks(core),
(themePaths, name) => { Object.keys(themes).reduce((map, entry) => reducePacks(themes[entry], map), {})
const themeData = themes[name];
themePaths[`themes/${name}`] = resolve(themeData.pack_directory, themeData.pack);
return themePaths;
}, {}
)
), ),
output: { output: {
@ -64,7 +68,7 @@ module.exports = {
writeToFileEmit: true, writeToFileEmit: true,
}), }),
new webpack.optimize.CommonsChunkPlugin({ new webpack.optimize.CommonsChunkPlugin({
name: 'common', name: 'locales',
minChunks: Infinity, // It doesn't make sense to use common chunks with multiple frontend support. minChunks: Infinity, // It doesn't make sense to use common chunks with multiple frontend support.
}), }),
], ],

View File

@ -2,7 +2,6 @@
default: &default default: &default
source_path: app/javascript source_path: app/javascript
source_entry_path: packs
public_output_path: packs public_output_path: packs
cache_path: tmp/cache/webpacker cache_path: tmp/cache/webpacker
@ -13,17 +12,6 @@ default: &default
# Reload manifest.json on all requests so we reload latest compiled packs # Reload manifest.json on all requests so we reload latest compiled packs
cache_manifest: false cache_manifest: false
extensions:
- .js
- .sass
- .scss
- .css
- .png
- .svg
- .gif
- .jpeg
- .jpg
development: development:
<<: *default <<: *default
compile: true compile: true