Commit 050a3bb9 authored by Emma's avatar Emma 🦉

add user preference for selecting preferred fonts

parent efa21b22
Pipeline #55443340 passed with stages
in 8 minutes and 16 seconds
......@@ -19,6 +19,8 @@ APP_ENABLE_WEBHOOKS=0
# No whitelisting by default
RATELIMIT_WHITELIST=
APP_FONTS=%kernel.project_dir%/assets/fonts.json
###> symfony/framework-bundle ###
APP_ENV=dev
APP_SECRET="change this in prod or die"
......
......@@ -14,14 +14,14 @@ audit-php:
audit-node:
stage: audit
image: node:8
image: node:10
allow_failure: true
script:
- yarn audit
.build-assets: &build-assets
stage: build-assets
image: node:8
image: node:10
except:
- schedules
cache:
......
......@@ -13,4 +13,15 @@
margin-top: -0.5rem;
margin-bottom: 1rem;
}
p, h1, h2, h3, h4, h5, h6, ol, ul {
margin-bottom: 0.5rem;
}
ol,
ul {
> & {
margin-bottom: 0;
}
}
}
@font-face {
font-family: OpenDyslexic;
src: url(../../fonts/OpenDyslexic3-Regular.ttf);
}
@font-face {
font-family: OpenDyslexic-Bold;
src: url(../../fonts/OpenDyslexic3-Bold.ttf);
}
@import '~typeface-roboto';
@import '~typeface-ubuntu';
@import '~typeface-roboto';
@import '~typeface-roboto-mono';
/*! https://postmill.xyz/ */
:root {
--font-family: Roboto, Helvetica Neue, Helvetica, Arial, sans-serif;
--font-size: 14px;
--line-height: 1.5;
--mono-font-family: Roboto Mono, monospace;
......
@import '~typeface-roboto';
@import '~typeface-roboto-mono';
/*! https://postmill.xyz/ */
:root {
--font-family: Roboto, Helvetica Neue, Helvetica, Arial, sans-serif;
--font-size: 14px;
--line-height: 1.5;
--mono-font-family: Roboto Mono, monospace;
......
{
"default": {
"alias": ["Roboto", "sans-serif"],
"entrypoint": "fonts/Roboto"
},
"system": {
"alias": [
"-apple-system",
"BlinkMacSystemFont",
"Segoe UI",
"Roboto",
"Helvetica",
"Arial",
"sans-serif",
"Apple Color Emoji",
"Segoe UI Emoji",
"Segoe UI Symbol"
]
},
"opendyslexic": {
"entrypoint": "fonts/OpenDyslexic"
},
"roboto": {
"entrypoint": "fonts/Roboto"
},
"ubuntu": {
"entrypoint": "fonts/Ubuntu"
}
}
This directory contains fonts that aren't available as NPM packages.
......@@ -6,6 +6,7 @@ imports:
parameters:
days_to_keep_logs: 7
fonts_config: "%env(json:file:resolve:APP_FONTS)%"
ratelimit_ip_whitelist: "%env(csv:RATELIMIT_WHITELIST)%"
submission_sort_modes: hot|new|top|controversial|most_commented
user_forum_creation_interval: 1 day
......@@ -20,6 +21,7 @@ services:
bind:
$defaultLocale: "%env(APP_LOCALE)%"
$enableWebhooks: "%env(bool:APP_ENABLE_WEBHOOKS)%"
$fontsConfig: "%fonts_config%"
$secret: "%env(APP_SECRET)%"
$siteName: "%env(SITE_NAME)%"
public: false
......
......@@ -18,7 +18,8 @@
"normalize.css": "^8.0.1",
"select2": "^4.0.5",
"typeface-roboto": "0.0.54",
"typeface-roboto-mono": "0.0.54"
"typeface-roboto-mono": "0.0.54",
"typeface-ubuntu": "^0.0.65"
},
"scripts": {
"build-dev": "encore dev",
......
......@@ -273,6 +273,13 @@ class User implements UserInterface, EquatableInterface {
*/
private $notifyOnMentions = true;
/**
* @ORM\Column(type="text", nullable=true)
*
* @var string|null
*/
private $preferredFonts;
public function __construct(string $username, string $password, \DateTime $created = null) {
$this->setUsername($username);
$this->password = $password;
......@@ -677,6 +684,14 @@ class User implements UserInterface, EquatableInterface {
$this->notifyOnMentions = $notifyOnMentions;
}
public function getPreferredFonts(): ?string {
return $this->preferredFonts;
}
public function setPreferredFonts(?string $preferredFonts): void {
$this->preferredFonts = $preferredFonts;
}
/**
* Returns the normalized form of the username.
*
......
......@@ -84,6 +84,13 @@ class UserData implements UserInterface {
private $notifyOnMentions;
/**
* @Assert\Length(max=200, groups={"settings"})
*
* @var string|null
*/
private $preferredFonts;
private $admin = false;
public static function fromUser(User $user): self {
......@@ -103,6 +110,7 @@ class UserData implements UserInterface {
$self->showThumbnails = $user->showThumbnails();
$self->notifyOnReply = $user->getNotifyOnReply();
$self->notifyOnMentions = $user->getNotifyOnMentions();
$self->preferredFonts = $user->getPreferredFonts();
$self->admin = $user->isAdmin();
return $self;
......@@ -128,6 +136,7 @@ class UserData implements UserInterface {
$user->setShowThumbnails($this->showThumbnails);
$user->setNotifyOnReply($this->notifyOnReply);
$user->setNotifyOnMentions($this->notifyOnMentions);
$user->setPreferredFonts($this->preferredFonts);
$user->setAdmin($this->admin);
}
......@@ -149,6 +158,7 @@ class UserData implements UserInterface {
'showThumbnails',
'notifyOnReply',
'notifyOnMentions',
'preferredFonts',
];
foreach ($settings as $setting) {
......@@ -300,6 +310,14 @@ class UserData implements UserInterface {
$this->notifyOnMentions = $notifyOnMentions;
}
public function getPreferredFonts(): ?string {
return $this->preferredFonts;
}
public function setPreferredFonts(?string $preferredFonts): void {
$this->preferredFonts = $preferredFonts;
}
public function isAdmin(): bool {
return $this->admin;
}
......
......@@ -9,6 +9,7 @@ use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Intl\Intl;
use Symfony\Component\OptionsResolver\OptionsResolver;
......@@ -27,29 +28,9 @@ final class UserSettingsType extends AbstractType {
* {@inheritdoc}
*/
public function buildForm(FormBuilderInterface $builder, array $options) {
$localeChoices = [];
$localeBundle = Intl::getLocaleBundle();
foreach ($this->availableLocales as $locale) {
$name = $localeBundle->getLocaleName($locale, $locale);
$localeChoices[$name] = $locale;
}
\uksort($localeChoices, function ($a, $b) {
[$a, $b] = \array_map(function ($key) {
return \transliterator_transliterate(
'NFKD; Latin; Latin/US-ASCII; [:Nonspacing Mark:] Remove; Lower',
$key
);
}, [$a, $b]);
return \strnatcasecmp($a, $b);
});
$builder
->add('locale', ChoiceType::class, [
'choices' => $localeChoices,
'choices' => $this->buildLocaleChoices(),
'choice_translation_domain' => false,
])
->add('front_page', ChoiceType::class, [
......@@ -98,6 +79,9 @@ final class UserSettingsType extends AbstractType {
'label' => 'label.notify_on_mentions',
'required' => false,
])
->add('preferredFonts', TextType::class, [
'required' => false,
])
->add('save', SubmitType::class);
}
......@@ -111,4 +95,28 @@ final class UserSettingsType extends AbstractType {
'validation_groups' => ['settings'],
]);
}
private function buildLocaleChoices(): array {
$localeChoices = [];
$localeBundle = Intl::getLocaleBundle();
foreach ($this->availableLocales as $locale) {
$name = $localeBundle->getLocaleName($locale, $locale);
$localeChoices[$name] = $locale;
}
\uksort($localeChoices, function ($a, $b) {
[$a, $b] = \array_map(function ($key) {
return \transliterator_transliterate(
'NFKD; Latin; Latin/US-ASCII; [:Nonspacing Mark:] Remove; Lower',
$key
);
}, [$a, $b]);
return \strnatcasecmp($a, $b);
});
return $localeChoices;
}
}
<?php
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20190405122449 extends AbstractMigration {
public function getDescription(): string {
return 'Add preferred fonts column for users';
}
public function up(Schema $schema): void {
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE users ADD preferred_fonts TEXT DEFAULT NULL');
}
public function down(Schema $schema): void {
$this->abortIf($this->connection->getDatabasePlatform()->getName() !== 'postgresql', 'Migration can only be executed safely on \'postgresql\'.');
$this->addSql('ALTER TABLE users DROP preferred_fonts');
}
}
......@@ -30,11 +30,23 @@ final class AppExtension extends AbstractExtension {
*/
private $enableWebhooks;
public function __construct(string $siteName, ?string $branch, ?string $version, bool $enableWebhooks) {
/**
* @var array
*/
private $fontsConfig;
public function __construct(
string $siteName,
?string $branch,
?string $version,
bool $enableWebhooks,
array $fontsConfig
) {
$this->siteName = $siteName;
$this->branch = $branch;
$this->version = $version;
$this->enableWebhooks = $enableWebhooks;
$this->fontsConfig = $fontsConfig;
}
public function getFunctions(): array {
......@@ -51,6 +63,19 @@ final class AppExtension extends AbstractExtension {
new TwigFunction('app_webhooks_enabled', function () {
return $this->enableWebhooks;
}),
new TwigFunction('font_list', function (): array {
return \array_keys($this->fontsConfig);
}),
new TwigFunction('font_names', function (string $font): array {
$font = \strtolower($font);
return $this->fontsConfig[$font]['alias'] ?? [$font];
}),
new TwigFunction('font_entrypoint', function (string $font): ?string {
$font = \strtolower($font);
return $this->fontsConfig[$font]['entrypoint'] ?? null;
}),
];
}
}
......@@ -47,13 +47,19 @@
{% block form_help -%}
{%- if help is not empty -%}
{%- set help_attr = help_attr|merge({class: (help_attr.class|default('') ~ ' form-help')|trim}) -%}
<p id="{{ id }}_help"{% with { attr: help_attr } %}{{ block('attributes') }}{% endwith %}>
{%- if translation_domain is same as(false) -%}
{{- help -}}
{%- else -%}
{{- help|trans({}, translation_domain) -}}
{%- endif -%}
</p>
{%- if not (raw_help ?? false) -%}
<p id="{{ id }}_help"{% with { attr: help_attr } %}{{ block('attributes') }}{% endwith %}>
{%- if translation_domain is same as(false) -%}
{{- help -}}
{%- else -%}
{{- help|trans({}, translation_domain) -}}
{%- endif -%}
</p>
{%- else -%}
<div id="{{ id }}_help"{% with { attr: help_attr } %}{{ block('attributes') }}{% endwith %}>
{{- help|raw -}}
</div>
{%- endif -%}
{%- endif -%}
{%- endblock form_help %}
......
{% set hierarchy = revision.hierarchy ?? theme.latestRevision.hierarchy ?? [] %}
{%- set fonts = (app.user.preferredFonts ?? 'default')|trim(', ')|split(',') %}
{%- set font_family = '' %}
{%- for font in fonts %}
{%- set font = font|trim %}
{%- set entrypoint = font_entrypoint(font) %}
{%- if entrypoint %}
{{- encore_entry_link_tags(entrypoint) }}
{%- endif %}
{%- for name in font_names(font) %}
{%- set font_family = font_family~' "'~(name|e('css'))~'",' %}
{%- endfor %}
{%- endfor %}
<style>:root { --font-family: {{ font_family|trim(' ,')|raw }} }</style>
{% if hierarchy[0].appendToDefaultStyle ?? true %}
{% if not night_mode %}
{{ encore_entry_link_tags('postmill') }}
......
......@@ -40,6 +40,17 @@
<fieldset class="fieldset">
<legend>{{ 'label.appearance'|trans }}</legend>
{{ form_row(form.preferredFonts, {
attr: {
placeholder: font_names('default')|join(', '),
'aria-describedby': 'user_settings_preferred_fonts',
},
help: block('_preferred_fonts_help'),
label: 'label.preferred_fonts'|trans,
translation_domain: false,
raw_help: true,
}) }}
{{ form_row(form.preferred_theme, {attr: {class: 'select2'}}) }}
{{ form_row(form.show_custom_stylesheets) }}
......@@ -48,3 +59,21 @@
</fieldset>
{{ form_end(form) }}
{% endblock %}
{% block _preferred_fonts_help %}
<p>{{ 'help.preferred_fonts'|trans }}</p>
<p>
{{ 'help.preferred_fonts_served'|trans }}
{%- for font in font_list() if font_names(font) == [font] %}
{{- loop.index != 1 ? ', ' -}}
<strong>{{ font|capitalize }}</strong>
{%- endfor %}
</p>
<p>
{{ 'help.preferred_fonts_aliases'|trans }}
{%- for font in font_list() if font_names(font) != [font] %}
{{- loop.index != 1 ? ', ' -}}
<strong><abbr title="{{ font_names(font)|join(', ') }}">{{ font|capitalize }}</abbr></strong>
{%- endfor %}
</p>
{% endblock %}
......@@ -219,6 +219,9 @@ help:
required_field: This field is required.
notify_on_reply: Get notifications when someone replies to your posts.
notify_on_mentions: Get notifications when someone links to your user profile.
preferred_fonts: 'Comma-separated list of the fonts you prefer. These can be fonts from your system, or from the list of server-provided fonts below.'
preferred_fonts_served: 'Server-provided fonts (available everywhere): '
preferred_fonts_aliases: 'Aliased font names: '
inbox:
message_reply_head: 'Re: %title%'
......@@ -326,6 +329,7 @@ label:
from_time: 'From: %time%'
last_message: Last message
participants: Participants
preferred_fonts: Preferred font(s)
log:
comment_deletion: '%user% deleted comment by %author% in "%submission%"'
......
'use strict';
const Encore = require('@symfony/webpack-encore');
const fs = require('fs');
const merge = require('webpack-merge');
Encore
.addEntry('main', './assets/js/main.js')
.addStyleEntry('core', './assets/css/core.less')
.addStyleEntry('postmill', './assets/css/postmill.css')
.addStyleEntry('postmill-night', './assets/css/postmill-night.css')
.cleanupOutputBeforeBuild()
.copyFiles({
from: './assets/icons',
......@@ -23,6 +21,24 @@ Encore
.setPublicPath('/build')
.createSharedEntry('vendor', './assets/js/vendor.js');
(function addStyleEntrypoints(directory, prefix) {
fs.readdirSync(directory, { withFileTypes: true }).forEach(function (file) {
if (file.name[0] !== '_') {
const filePath = directory + '/' + file.name;
if (file.isFile() && /\.(le|c)ss$/i.test(file.name)) {
const entryName = prefix + file.name.replace(/\..+?$/, '');
Encore.addStyleEntry(entryName, filePath);
} else if (file.isDirectory()) {
const newPrefix = prefix + file.name + '/';
addStyleEntrypoints(filePath, newPrefix);
}
}
});
})(__dirname + '/assets/css', '');
module.exports = merge(Encore.getWebpackConfig(), {
externals: {
"fosjsrouting": "Routing",
......
......@@ -6238,6 +6238,11 @@ typeface-roboto@0.0.54:
resolved "https://registry.yarnpkg.com/typeface-roboto/-/typeface-roboto-0.0.54.tgz#8f02c9a18d1cfa7f49381a6ff0d21ff061f38ad2"
integrity sha512-sOFA1FXgP0gOgBYlS6irwq6hHYA370KE3dPlgYEJHL3PJd5X8gQE0RmL79ONif6fL5JZuGDj+rtOrFeOqz5IZQ==
typeface-ubuntu@^0.0.65:
version "0.0.65"
resolved "https://registry.yarnpkg.com/typeface-ubuntu/-/typeface-ubuntu-0.0.65.tgz#d2994cc4b1540ed4830cef7aae2a8c393c197a74"
integrity sha512-v+s/216IINmqQoO3Y9+bKDn9OgrasPtRLWaVSmjRBljFDkFndmQjHcsLZrv0r89aSeqzxlBvKwZIigFh8pZO9Q==
unicode-canonical-property-names-ecmascript@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818"
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment