FEATURE: Add site setting and wizard step to set base font (#10250)

Co-authored-by: Neil Lalonde <neillalonde@gmail.com>
This commit is contained in:
Bianca Nenciu 2020-08-31 13:14:09 +03:00 committed by GitHub
parent 7290ca1f4a
commit f2e14a3946
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 404 additions and 60 deletions

2
.gitignore vendored
View File

@ -87,6 +87,7 @@ config/multisite.yml
config/multisite1.yml
config/fog_credentials.yml
/public/fonts
/public/uploads
/public/backups
/public/stylesheet-cache/*
@ -131,6 +132,7 @@ node_modules
# ignore auto-generated plugin js assets
/app/assets/javascripts/plugins/*
/app/assets/stylesheets/common/fonts.scss
# ignore generated api documentation files
openapi/*

View File

@ -69,6 +69,7 @@ gem 'http_accept_language', require: false
gem 'ember-rails', '0.18.5'
gem 'discourse-ember-source', '~> 3.12.2'
gem 'ember-handlebars-template', '0.8.0'
gem 'discourse-fonts'
gem 'barber'

View File

@ -99,6 +99,7 @@ GEM
diff-lcs (1.4.4)
diffy (3.4.0)
discourse-ember-source (3.12.2.2)
discourse-fonts (0.0.2)
discourse_image_optim (0.26.2)
exifr (~> 1.2, >= 1.2.2)
fspath (~> 3.0)
@ -461,6 +462,7 @@ DEPENDENCIES
css_parser
diffy
discourse-ember-source (~> 3.12.2)
discourse-fonts
discourse_image_optim
email_reply_trimmer
ember-handlebars-template (= 0.8.0)

View File

@ -0,0 +1,125 @@
import I18n from "I18n";
import discourseComputed from "discourse-common/utils/decorators";
import { observes } from "discourse-common/utils/decorators";
import {
createPreviewComponent,
darkLightDiff,
chooseDarker,
LOREM
} from "wizard/lib/preview";
export default createPreviewComponent(305, 165, {
logo: null,
avatar: null,
classNameBindings: ["isSelected"],
@discourseComputed("selectedId", "fontId")
isSelected(selectedId, fontId) {
return selectedId === fontId;
},
click() {
this.onChange(this.fontId);
},
@observes("step.fieldsById.base_scheme_id.value")
themeChanged() {
this.triggerRepaint();
},
images() {
return {
logo: this.wizard.getLogoUrl(),
avatar: "/images/wizard/trout.png"
};
},
paint(ctx, colors, font, width, height) {
const headerHeight = height * 0.3;
this.drawFullHeader(colors, font);
const margin = width * 0.04;
const avatarSize = height * 0.2;
const lineHeight = height / 9.5;
// Draw a fake topic
this.scaleImage(
this.avatar,
margin,
headerHeight + height * 0.085,
avatarSize,
avatarSize
);
const titleFontSize = headerHeight / 44;
ctx.beginPath();
ctx.fillStyle = colors.primary;
ctx.font = `bold ${titleFontSize}em '${font}'`;
ctx.fillText(I18n.t("wizard.previews.topic_title"), margin, height * 0.3);
const bodyFontSize = height / 220.0;
ctx.font = `${bodyFontSize}em '${font}'`;
let line = 0;
const lines = LOREM.split("\n");
for (let i = 0; i < 4; i++) {
line = height * 0.35 + i * lineHeight;
ctx.fillText(lines[i], margin + avatarSize + margin, line);
}
// Share Button
ctx.beginPath();
ctx.rect(margin, line + lineHeight, width * 0.14, height * 0.14);
ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 90, 65);
ctx.fill();
ctx.fillStyle = chooseDarker(colors.primary, colors.secondary);
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(
I18n.t("wizard.previews.share_button"),
margin + width / 55,
line + lineHeight * 1.85
);
// Reply Button
ctx.beginPath();
ctx.rect(
margin * 2 + width * 0.14,
line + lineHeight,
width * 0.14,
height * 0.14
);
ctx.fillStyle = colors.tertiary;
ctx.fill();
ctx.fillStyle = colors.secondary;
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(
I18n.t("wizard.previews.reply_button"),
margin * 2 + width * 0.14 + width / 55,
line + lineHeight * 1.85
);
// Draw Timeline
const timelineX = width * 0.8;
ctx.beginPath();
ctx.strokeStyle = colors.tertiary;
ctx.lineWidth = 0.5;
ctx.moveTo(timelineX, height * 0.3);
ctx.lineTo(timelineX, height * 0.7);
ctx.stroke();
// Timeline
ctx.beginPath();
ctx.strokeStyle = colors.tertiary;
ctx.lineWidth = 2;
ctx.moveTo(timelineX, height * 0.3);
ctx.lineTo(timelineX, height * 0.4);
ctx.stroke();
ctx.font = `Bold ${bodyFontSize}em ${font}`;
ctx.fillStyle = colors.primary;
ctx.fillText("1 / 20", timelineX + margin, height * 0.3 + margin * 1.5);
}
});

View File

@ -0,0 +1,8 @@
import Component from "@ember/component";
export default Component.extend({
actions: {
changed(value) {
this.set("field.value", value);
}
}
});

View File

@ -21,36 +21,36 @@ export default createPreviewComponent(659, 320, {
};
},
paint(ctx, colors, width, height) {
this.drawFullHeader(colors);
paint(ctx, colors, font, width, height) {
this.drawFullHeader(colors, font);
if (this.get("step.fieldsById.homepage_style.value") === "latest") {
this.drawPills(colors, height * 0.15);
this.renderLatest(ctx, colors, width, height);
this.drawPills(colors, font, height * 0.15);
this.renderLatest(ctx, colors, font, width, height);
} else if (
["categories_only", "categories_with_featured_topics"].includes(
this.get("step.fieldsById.homepage_style.value")
)
) {
this.drawPills(colors, height * 0.15, { categories: true });
this.renderCategories(ctx, colors, width, height);
this.drawPills(colors, font, height * 0.15, { categories: true });
this.renderCategories(ctx, colors, font, width, height);
} else if (
["categories_boxes", "categories_boxes_with_topics"].includes(
this.get("step.fieldsById.homepage_style.value")
)
) {
this.drawPills(colors, height * 0.15, { categories: true });
this.drawPills(colors, font, height * 0.15, { categories: true });
const topics =
this.get("step.fieldsById.homepage_style.value") ===
"categories_boxes_with_topics";
this.renderCategoriesBoxes(ctx, colors, width, height, { topics });
this.renderCategoriesBoxes(ctx, colors, font, width, height, { topics });
} else {
this.drawPills(colors, height * 0.15, { categories: true });
this.renderCategoriesWithTopics(ctx, colors, width, height);
this.drawPills(colors, font, height * 0.15, { categories: true });
this.renderCategoriesWithTopics(ctx, colors, font, width, height);
}
},
renderCategoriesBoxes(ctx, colors, width, height, opts) {
renderCategoriesBoxes(ctx, colors, font, width, height, opts) {
opts = opts || {};
const borderColor = darkLightDiff(
@ -83,7 +83,7 @@ export default createPreviewComponent(659, 320, {
]
);
ctx.font = `Bold ${bodyFontSize * 1.3}em 'Arial'`;
ctx.font = `Bold ${bodyFontSize * 1.3}em '${font}'`;
ctx.fillStyle = colors.primary;
ctx.textAlign = "center";
ctx.fillText(category.name, boxStartX + boxWidth / 2, boxStartY + 25);
@ -92,7 +92,7 @@ export default createPreviewComponent(659, 320, {
if (opts.topics) {
let startY = boxStartY + 60;
this.getTitles().forEach(title => {
ctx.font = `${bodyFontSize * 1}em 'Arial'`;
ctx.font = `${bodyFontSize * 1}em '${font}'`;
ctx.fillStyle = colors.tertiary;
startY +=
this.fillTextMultiLine(
@ -105,7 +105,7 @@ export default createPreviewComponent(659, 320, {
) + 8;
});
} else {
ctx.font = `${bodyFontSize * 1}em 'Arial'`;
ctx.font = `${bodyFontSize * 1}em '${font}'`;
ctx.fillStyle = textColor;
ctx.textAlign = "center";
this.fillTextMultiLine(
@ -121,7 +121,7 @@ export default createPreviewComponent(659, 320, {
});
},
renderCategories(ctx, colors, width, height) {
renderCategories(ctx, colors, font, width, height) {
const textColor = darkLightDiff(colors.primary, colors.secondary, 50, 50);
const margin = height * 0.03;
const bodyFontSize = height / 440.0;
@ -144,7 +144,7 @@ export default createPreviewComponent(659, 320, {
const cols = [0.025, 0.45, 0.53, 0.58, 0.94, 0.96].map(c => c * width);
const headingY = height * 0.33;
ctx.font = `${bodyFontSize * 0.9}em 'Arial'`;
ctx.font = `${bodyFontSize * 0.9}em '${font}'`;
ctx.fillStyle = textColor;
ctx.fillText("Category", cols[0], headingY);
if (
@ -165,11 +165,11 @@ export default createPreviewComponent(659, 320, {
// Categories
this.categories().forEach(category => {
const textPos = y + categoryHeight * 0.35;
ctx.font = `Bold ${bodyFontSize * 1.1}em 'Arial'`;
ctx.font = `Bold ${bodyFontSize * 1.1}em '${font}'`;
ctx.fillStyle = colors.primary;
ctx.fillText(category.name, cols[0], textPos);
ctx.font = `${bodyFontSize * 0.8}em 'Arial'`;
ctx.font = `${bodyFontSize * 0.8}em '${font}'`;
ctx.fillStyle = textColor;
ctx.fillText(
titles[0],
@ -188,14 +188,14 @@ export default createPreviewComponent(659, 320, {
this.get("step.fieldsById.homepage_style.value") ===
"categories_with_featured_topics"
) {
ctx.font = `${bodyFontSize}em 'Arial'`;
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(
Math.floor(Math.random() * 90) + 10,
cols[1] + 15,
textPos
);
} else {
ctx.font = `${bodyFontSize}em 'Arial'`;
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(Math.floor(Math.random() * 90) + 10, cols[5], textPos);
}
@ -216,7 +216,7 @@ export default createPreviewComponent(659, 320, {
ctx.fillStyle = colors.tertiary;
titles.forEach(title => {
ctx.font = `${bodyFontSize}em 'Arial'`;
ctx.font = `${bodyFontSize}em '${font}'`;
const textPos = y + topicHeight * 0.35;
ctx.fillStyle = colors.tertiary;
ctx.fillText(`${title}`, cols[2], textPos);
@ -225,7 +225,7 @@ export default createPreviewComponent(659, 320, {
}
},
renderCategoriesWithTopics(ctx, colors, width, height) {
renderCategoriesWithTopics(ctx, colors, font, width, height) {
const textColor = darkLightDiff(colors.primary, colors.secondary, 50, 50);
const margin = height * 0.03;
const bodyFontSize = height / 440.0;
@ -246,7 +246,7 @@ export default createPreviewComponent(659, 320, {
const cols = [0.025, 0.42, 0.53, 0.58, 0.94].map(c => c * width);
const headingY = height * 0.33;
ctx.font = `${bodyFontSize * 0.9}em 'Arial'`;
ctx.font = `${bodyFontSize * 0.9}em '${font}'`;
ctx.fillStyle = textColor;
ctx.fillText("Category", cols[0], headingY);
ctx.fillText("Topics", cols[1], headingY);
@ -270,11 +270,11 @@ export default createPreviewComponent(659, 320, {
// Categories
this.categories().forEach(category => {
const textPos = y + categoryHeight * 0.35;
ctx.font = `Bold ${bodyFontSize * 1.1}em 'Arial'`;
ctx.font = `Bold ${bodyFontSize * 1.1}em '${font}'`;
ctx.fillStyle = colors.primary;
ctx.fillText(category.name, cols[0], textPos);
ctx.font = `${bodyFontSize * 0.8}em 'Arial'`;
ctx.font = `${bodyFontSize * 0.8}em '${font}'`;
ctx.fillStyle = textColor;
ctx.fillText(
titles[0],
@ -289,7 +289,7 @@ export default createPreviewComponent(659, 320, {
ctx.lineTo(margin, y + categoryHeight);
ctx.stroke();
ctx.font = `${bodyFontSize}em 'Arial'`;
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(Math.floor(Math.random() * 90) + 10, cols[1] + 15, textPos);
y += categoryHeight;
@ -306,7 +306,7 @@ export default createPreviewComponent(659, 320, {
titles.forEach(title => {
const category = this.categories()[0];
ctx.font = `${bodyFontSize}em 'Arial'`;
ctx.font = `${bodyFontSize}em '${font}'`;
const textPos = y + topicHeight * 0.45;
ctx.fillStyle = textColor;
this.scaleImage(
@ -318,15 +318,15 @@ export default createPreviewComponent(659, 320, {
);
ctx.fillText(title, cols[3], textPos);
ctx.font = `Bold ${bodyFontSize}em 'Arial'`;
ctx.font = `Bold ${bodyFontSize}em '${font}'`;
ctx.fillText(Math.floor(Math.random() * 90) + 10, cols[4], textPos);
ctx.font = `${bodyFontSize}em 'Arial'`;
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(`1h`, cols[4], textPos + topicHeight * 0.4);
ctx.beginPath();
ctx.fillStyle = category.color;
const badgeSize = topicHeight * 0.1;
ctx.font = `Bold ${bodyFontSize * 0.5}em 'Arial'`;
ctx.font = `Bold ${bodyFontSize * 0.5}em '${font}'`;
ctx.rect(
cols[3] + margin * 0.5,
y + topicHeight * 0.65,
@ -357,12 +357,12 @@ export default createPreviewComponent(659, 320, {
return LOREM.split(".");
},
renderLatest(ctx, colors, width, height) {
renderLatest(ctx, colors, font, width, height) {
const rowHeight = height / 6.6;
const textColor = darkLightDiff(colors.primary, colors.secondary, 50, 50);
const bodyFontSize = height / 440.0;
ctx.font = `${bodyFontSize}em 'Arial'`;
ctx.font = `${bodyFontSize}em '${font}'`;
const margin = height * 0.03;
@ -385,7 +385,7 @@ export default createPreviewComponent(659, 320, {
const headingY = height * 0.33;
ctx.fillStyle = textColor;
ctx.font = `${bodyFontSize * 0.9}em 'Arial'`;
ctx.font = `${bodyFontSize * 0.9}em '${font}'`;
ctx.fillText("Topic", cols[0], headingY);
ctx.fillText("Replies", cols[2], headingY);
ctx.fillText("Views", cols[3], headingY);
@ -396,7 +396,7 @@ export default createPreviewComponent(659, 320, {
ctx.lineWidth = 2;
drawLine(y);
ctx.font = `${bodyFontSize}em 'Arial'`;
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.lineWidth = 1;
this.getTitles().forEach(title => {
const textPos = y + rowHeight * 0.4;
@ -407,7 +407,7 @@ export default createPreviewComponent(659, 320, {
ctx.beginPath();
ctx.fillStyle = category.color;
const badgeSize = rowHeight * 0.15;
ctx.font = `Bold ${bodyFontSize * 0.75}em 'Arial'`;
ctx.font = `Bold ${bodyFontSize * 0.75}em '${font}'`;
ctx.rect(cols[0] + 4, y + rowHeight * 0.6, badgeSize, badgeSize);
ctx.fill();
@ -426,7 +426,7 @@ export default createPreviewComponent(659, 320, {
);
ctx.fillStyle = textColor;
ctx.font = `${bodyFontSize}em 'Arial'`;
ctx.font = `${bodyFontSize}em '${font}'`;
for (let j = 2; j <= 4; j++) {
ctx.fillText(
j === 4 ? "1h" : Math.floor(Math.random() * 90) + 10,

View File

@ -14,7 +14,7 @@ export default createPreviewComponent(371, 124, {
return { tab: "/images/wizard/tab.png", image: this.get("field.value") };
},
paint(ctx, colors, width, height) {
paint(ctx, colors, font, width, height) {
this.scaleImage(this.tab, 0, 0, width, height);
this.scaleImage(this.image, 40, 25, 30, 30);

View File

@ -17,7 +17,7 @@ export default createPreviewComponent(325, 125, {
};
},
paint(ctx, colors, width, height) {
paint(ctx, colors, font, width, height) {
this.scaleImage(this.image, 10, 8, 87, 87);
this.scaleImage(this.ios, 0, 0, width, height);
}

View File

@ -13,7 +13,7 @@ export default createPreviewComponent(375, 100, {
return { image: this.get("field.value") };
},
paint(ctx, colors, width, height) {
paint(ctx, colors, font, width, height) {
const headerHeight = height / 2;
drawHeader(ctx, colors, width, headerHeight);
@ -39,7 +39,7 @@ export default createPreviewComponent(375, 100, {
const afterLogo = headerMargin * 1.7 + imageWidth;
const fontSize = Math.round(headerHeight * 0.4);
ctx.font = `Bold ${fontSize}px 'Arial'`;
ctx.font = `Bold ${fontSize}px '${font}'`;
ctx.fillStyle = colors.primary;
const title = LOREM.substring(0, 27);
ctx.fillText(
@ -55,7 +55,7 @@ export default createPreviewComponent(375, 100, {
ctx.rect(afterLogo, headerHeight * 0.7, badgeSize, badgeSize);
ctx.fill();
ctx.font = `Bold ${badgeSize * 1.2}px 'Arial'`;
ctx.font = `Bold ${badgeSize * 1.2}px '${font}'`;
ctx.fillStyle = colors.primary;
ctx.fillText(
category.name,
@ -64,7 +64,7 @@ export default createPreviewComponent(375, 100, {
);
const LINE_HEIGHT = 12;
ctx.font = `${LINE_HEIGHT}px 'Arial'`;
ctx.font = `${LINE_HEIGHT}px '${font}'`;
const lines = LOREM.split("\n");
for (let i = 0; i < 10; i++) {
const line = height * 0.55 + i * (LINE_HEIGHT * 1.5);

View File

@ -13,7 +13,7 @@ export default createPreviewComponent(400, 100, {
return { image: this.get("field.value") };
},
paint(ctx, colors, width, height) {
paint(ctx, colors, font, width, height) {
const headerHeight = height / 2;
drawHeader(ctx, colors, width, headerHeight);
@ -31,6 +31,6 @@ export default createPreviewComponent(400, 100, {
imageHeight
);
this.drawPills(colors, height / 2);
this.drawPills(colors, font, height / 2);
}
});

View File

@ -35,10 +35,10 @@ export default createPreviewComponent(305, 165, {
};
},
paint(ctx, colors, width, height) {
paint(ctx, colors, font, width, height) {
const headerHeight = height * 0.3;
this.drawFullHeader(colors);
this.drawFullHeader(colors, font);
const margin = width * 0.04;
const avatarSize = height * 0.2;
@ -57,11 +57,11 @@ export default createPreviewComponent(305, 165, {
ctx.beginPath();
ctx.fillStyle = colors.primary;
ctx.font = `bold ${titleFontSize}em 'Arial'`;
ctx.font = `bold ${titleFontSize}em '${font}'`;
ctx.fillText(I18n.t("wizard.previews.topic_title"), margin, height * 0.3);
const bodyFontSize = height / 220.0;
ctx.font = `${bodyFontSize}em 'Arial'`;
ctx.font = `${bodyFontSize}em '${font}'`;
let line = 0;
const lines = LOREM.split("\n");
@ -76,7 +76,7 @@ export default createPreviewComponent(305, 165, {
ctx.fillStyle = darkLightDiff(colors.primary, colors.secondary, 90, 65);
ctx.fill();
ctx.fillStyle = chooseDarker(colors.primary, colors.secondary);
ctx.font = `${bodyFontSize}em 'Arial'`;
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(
I18n.t("wizard.previews.share_button"),
margin + width / 55,
@ -94,7 +94,7 @@ export default createPreviewComponent(305, 165, {
ctx.fillStyle = colors.tertiary;
ctx.fill();
ctx.fillStyle = colors.secondary;
ctx.font = `${bodyFontSize}em 'Arial'`;
ctx.font = `${bodyFontSize}em '${font}'`;
ctx.fillText(
I18n.t("wizard.previews.reply_button"),
margin * 2 + width * 0.14 + width / 55,
@ -118,7 +118,7 @@ export default createPreviewComponent(305, 165, {
ctx.lineTo(timelineX, height * 0.4);
ctx.stroke();
ctx.font = `Bold ${bodyFontSize}em Arial`;
ctx.font = `Bold ${bodyFontSize}em ${font}`;
ctx.fillStyle = colors.primary;
ctx.fillText("1 / 20", timelineX + margin, height * 0.3 + margin * 1.5);
}

View File

@ -7,5 +7,16 @@ export default Controller.extend({
@discourseComputed("currentStepId")
showCanvas(currentStepId) {
return currentStepId === "finished";
},
@discourseComputed("model")
fontClasses(model) {
const fontsStep = model.steps.findBy("id", "fonts");
if (!fontsStep) {
return [];
}
const fontField = fontsStep.get("fieldsById.font_previews");
return fontField.choices.map(choice => `font-${choice.data.class}`);
}
});

View File

@ -55,6 +55,10 @@ export function createPreviewComponent(width, height, obj) {
images() {},
loadFonts() {
return document.fonts.ready;
},
loadImages() {
const images = this.images();
if (images) {
@ -68,7 +72,7 @@ export function createPreviewComponent(width, height, obj) {
},
reload() {
this.loadImages().then(() => {
Promise.all([this.loadFonts(), this.loadImages()]).then(() => {
this.loaded = true;
this.triggerRepaint();
});
@ -88,12 +92,17 @@ export function createPreviewComponent(width, height, obj) {
return;
}
const font = this.wizard.getCurrentFont(this.fontId);
if (!font) {
return;
}
const { ctx } = this;
ctx.fillStyle = colors.secondary;
ctx.fillRect(0, 0, width, height);
this.paint(ctx, colors, this.width, this.height);
this.paint(ctx, colors, font, this.width, this.height);
// draw border
ctx.beginPath();
@ -133,7 +142,7 @@ export function createPreviewComponent(width, height, obj) {
ctx.drawImage(scaled[key], x, y, w, h);
},
drawFullHeader(colors) {
drawFullHeader(colors, font) {
const { ctx } = this;
const headerHeight = height * 0.15;
@ -147,7 +156,7 @@ export function createPreviewComponent(width, height, obj) {
ctx.beginPath();
ctx.fillStyle = colors.header_primary;
ctx.font = `bold ${logoHeight}px 'Arial'`;
ctx.font = `bold ${logoHeight}px '${font}'`;
ctx.fillText("Discourse", headerMargin, headerHeight - headerMargin);
// Top right menu
@ -188,7 +197,7 @@ export function createPreviewComponent(width, height, obj) {
ctx.restore();
},
drawPills(colors, headerHeight, opts) {
drawPills(colors, font, headerHeight, opts) {
opts = opts || {};
const { ctx } = this;
@ -210,7 +219,7 @@ export function createPreviewComponent(width, height, obj) {
const fontSize = Math.round(badgeHeight * 0.5);
ctx.font = `${fontSize}px 'Arial'`;
ctx.font = `${fontSize}px '${font}'`;
ctx.fillStyle = colors.primary;
ctx.fillText(
"all categories",
@ -246,7 +255,7 @@ export function createPreviewComponent(width, height, obj) {
);
ctx.fill();
ctx.font = `${fontSize}px 'Arial'`;
ctx.font = `${fontSize}px '${font}'`;
ctx.fillStyle = colors.secondary;
let x = headerMargin * 3.0 + categoriesSize;
ctx.fillText(

View File

@ -52,6 +52,35 @@ const Wizard = EmberObject.extend({
}
return option.data.colors;
},
getCurrentFont(fontId) {
const fontsStep = this.steps.findBy("id", "fonts");
if (!fontsStep) {
return;
}
const fontChoice = fontsStep.get("fieldsById.font_previews");
if (!fontChoice) {
return;
}
const choiceId = fontId ? fontId : fontChoice.get("value");
if (!choiceId) {
return;
}
const choices = fontChoice.get("choices");
if (!choices) {
return;
}
const option = choices.findBy("id", choiceId);
if (!option) {
return;
}
return option.data.font_stack.split(",")[0];
}
});

View File

@ -5,6 +5,13 @@
<div class="wizard-column">
<div class="wizard-column-contents">
{{outlet}}
{{!-- Load all font styles --}}
<div class="preloaded-font-styles">
{{#each fontClasses as |fontClass|}}
<span class={{fontClass}}>&nbsp;</span>
{{/each}}
</div>
</div>
<div class="wizard-footer">
<div class="discourse-logo"></div>

View File

@ -0,0 +1,8 @@
<div class="preview-area">
<canvas
width={{elementWidth}}
height={{elementHeight}}
style={{canvasStyle}}
>
</canvas>
</div>

View File

@ -0,0 +1,14 @@
<ul class="grid">
{{#each field.choices as |choice|}}
<li>
{{font-preview wizard=wizard
fontId=choice.id
selectedId=field.value
onChange=(action "changed")}}
{{radio-button radioValue=choice.id
label=choice.label
value=field.value
onChange=(action "changed")}}
</li>
{{/each}}
</ul>

View File

@ -13,3 +13,4 @@
@import "common/base/*";
@import "common/d-editor";
@import "common/topic-timeline";
@import "common/fonts";

View File

@ -46,7 +46,7 @@ $base-font-size-smaller: 0.875em !default; // eq. to 14px
$base-font-size: 0.938em !default; // eq. to 15px
$base-font-size-larger: 1.063em !default; // eq. to 17px
$base-font-size-largest: 1.118em !default; // eq. to 19px
$base-font-family: Helvetica, Arial, sans-serif !default;
$base-font-family: var(--font-family) !default;
// Font-size defintions, multiplier ^ (step / interval)
$font-up-6: 2.296em;

View File

@ -7,6 +7,7 @@
@import "common/foundation/mixins";
@import "common/select-kit/*";
@import "common/components/svg";
@import "common/fonts";
body.wizard {
background-color: #fff;
@ -131,7 +132,8 @@ body.wizard {
}
}
.wizard-step-colors {
.wizard-step-colors,
.wizard-step-fonts {
max-height: 465px;
overflow-y: auto;
.grid {
@ -189,6 +191,10 @@ body.wizard {
width: 100%;
border: 1px solid #ccc;
.preloaded-font-styles {
font-size: 1px;
}
.wizard-step-contents {
height: 550px;
margin-bottom: 2em;

View File

@ -119,6 +119,7 @@ module ApplicationHelper
list << 'ios-device' if ios_device?
list << 'rtl' if rtl?
list << text_size_class
list << font_class
list << 'anon' unless current_user
list.join(' ')
end
@ -151,6 +152,10 @@ module ApplicationHelper
"text-size-#{size}"
end
def font_class
"font-#{SiteSetting.base_font.tr("_", "-")}"
end
def escape_unicode(javascript)
if javascript
javascript = javascript.scrub

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
require "enum_site_setting"
class BaseFontSetting < EnumSiteSetting
def self.valid_value?(val)
values.any? { |v| v[:value].to_s == val.to_s }
end
def self.values
@values ||= DiscourseFonts.fonts.map do |font|
{ name: "base_font_setting.#{font[:key]}", value: font[:key] }
end
end
def self.translate_names?
true
end
end

View File

@ -53,6 +53,8 @@ end
require 'pry-rails' if Rails.env.development?
require 'discourse_fonts'
if defined?(Bundler)
bundler_groups = [:default]
@ -307,6 +309,32 @@ module Discourse
config.assets.precompile << "#{file}.js"
end
# Use discourse-fonts gem to symlink fonts and generate .scss file
fonts_path = File.join(config.root, 'public/fonts')
Discourse::Utils.atomic_ln_s(DiscourseFonts.path_for_fonts, fonts_path)
File.open(File.join(config.root, 'app/assets/stylesheets/common/fonts.scss'), 'w') do |file|
DiscourseFonts.fonts.each do |font|
file.write <<~EOF
.font-#{font[:key].tr("_", "-")} {
--font-family: #{font[:name]};
font-family: #{font[:name]};
}
EOF
if font[:variants].present?
font[:variants].each do |variant|
file.write <<~EOF
@font-face {
font-family: #{font[:name]};
src: asset-url("/fonts/#{variant[:filename]}") format("#{variant[:format]}");
font-weight: #{variant[:weight]};
}
EOF
end
end
end
end
require_dependency 'stylesheet/manager'
require_dependency 'svg_sprite/svg_sprite'

View File

@ -1733,6 +1733,31 @@ en:
categories_boxes: "Boxes with Subcategories"
categories_boxes_with_topics: "Boxes with Featured Topics"
base_font_setting:
helvetica: "Helvetica/Arial"
open_sans: "Open Sans"
oxanium: "Oxanium"
roboto: "Roboto"
lato: "Lato"
noto_sans_jp: "NotoSansJP"
montserrat: "Montserrat"
roboto_condensed: "RobotoCondensed"
source_sans_pro: "SourceSansPro"
oswald: "Oswald"
raleway: "Raleway"
roboto_mono: "RobotoMono"
poppins: "Poppins"
noto_sans: "NotoSans"
roboto_slab: "RobotoSlab"
merriweather: "Merriweather"
ubuntu: "Ubuntu"
pt_sans: "PTSans"
playfair_display: "PlayfairDisplay"
nunito: "Nunito"
lora: "Lora"
mukta: "Mukta"
shortcut_modifier_key:
shift: "Shift"
ctrl: "Ctrl"

View File

@ -2222,6 +2222,8 @@ en:
push_notifications_prompt: "Display user consent prompt."
push_notifications_icon: "The badge icon that appears in the notification corner. A 96×96 monochromatic PNG with transparency is recommended."
base_font: "Font to use in most places on the site. Themes can override."
short_title: "The short title will be used on the user's home screen, launcher, or other places where space may be limited. It should be limited to 12 characters."
dashboard_hidden_reports: "Allow to hide the specified reports from the dashboard."
@ -4680,6 +4682,9 @@ en:
</ul>
Popular theme components (for more, browse <a href='https://meta.discourse.org/c/theme/l/latest' target='_blank'>#theme</a>)"
fonts:
title: "Fonts"
logos:
title: "Logos"
fields:

View File

@ -323,6 +323,10 @@ basic:
vapid_base_url:
default: ""
hidden: true
base_font:
default: "helvetica"
enum: "BaseFontSetting"
refresh: true
login:
invite_only:

View File

@ -209,6 +209,22 @@ class Wizard
step.add_field(id: 'popular-themes', type: 'component')
end
@wizard.append_step('fonts') do |step|
field = step.add_field(
id: 'font_previews',
type: 'component',
value: SiteSetting.base_font
)
DiscourseFonts.fonts.each do |font|
field.add_choice(font[:key], data: { class: font[:key].tr("_", "-"), font_stack: font[:stack] })
end
step.on_update do |updater|
updater.update_setting(:base_font, updater.fields[:font_previews])
end
end
@wizard.append_step('logos') do |step|
step.add_field(id: 'logo', type: 'image', value: SiteSetting.site_logo_url)
step.add_field(id: 'logo_small', type: 'image', value: SiteSetting.site_logo_small_url)

View File

@ -167,6 +167,16 @@ describe Wizard::StepUpdater do
end
end
context "fonts step" do
it "updates the font" do
updater = wizard.create_updater('fonts', font_previews: 'open_sans')
updater.update
expect(updater.success?).to eq(true)
expect(wizard.completed_steps?('fonts')).to eq(true)
expect(SiteSetting.base_font).to eq('open_sans')
end
end
context "colors step" do
context "with an existing color scheme" do
fab!(:color_scheme) { Fabricate(:color_scheme, name: 'existing', via_wizard: true) }

View File

@ -41,6 +41,15 @@ describe Wizard::Builder do
expect(invites_step.disabled).to be_truthy
end
context 'fonts step' do
let(:fonts_step) { wizard.steps.find { |s| s.id == 'fonts' } }
let(:field) { fonts_step.fields.first }
it 'should set the right font' do
expect(field.choices.size).to eq(DiscourseFonts.fonts.size)
end
end
context 'logos step' do
let(:logos_step) { wizard.steps.find { |s| s.id == 'logos' } }