Design/home hero #54
No files matched your search
@@ -104,11 +104,17 @@ public class MarkdownTests
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void First_sentence_longer_than_the_limit_is_cut_between_words()
|
public void First_sentence_drops_a_colon_that_leads_into_a_list() =>
|
||||||
{
|
Markdown.FirstSentence("Formulare zum Herunterladen:\n\n- Antrag")
|
||||||
var sentence = Markdown.FirstSentence("Eins zwei drei vier fünf.", maxLength: 12);
|
.ShouldBe("Formulare zum Herunterladen");
|
||||||
|
|
||||||
sentence.ShouldBe("Eins zwei…");
|
[Fact]
|
||||||
|
public void First_sentence_is_never_cut_short()
|
||||||
|
{
|
||||||
|
// Long teasers are clamped by the tile's CSS, not by a character count.
|
||||||
|
var sentence = string.Join(' ', Enumerable.Repeat("Wort", 60)) + ".";
|
||||||
|
|
||||||
|
Markdown.FirstSentence(sentence).ShouldBe(sentence);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
@@ -119,8 +125,4 @@ public class MarkdownTests
|
|||||||
[InlineData("::: hinweis\nText im Baustein.\n:::")]
|
[InlineData("::: hinweis\nText im Baustein.\n:::")]
|
||||||
public void First_sentence_is_empty_without_a_top_level_paragraph(string? markdown) =>
|
public void First_sentence_is_empty_without_a_top_level_paragraph(string? markdown) =>
|
||||||
Markdown.FirstSentence(markdown).ShouldBe("");
|
Markdown.FirstSentence(markdown).ShouldBe("");
|
||||||
|
|
||||||
[Fact]
|
|
||||||
public void First_sentence_rejects_a_limit_below_one() =>
|
|
||||||
Should.Throw<ArgumentOutOfRangeException>(() => Markdown.FirstSentence("Text.", maxLength: 0));
|
|
||||||
}
|
}
|
||||||
@@ -38,8 +38,10 @@
|
|||||||
|
|
||||||
/* On the gradient everything is white: the global heading colour is a blue that
|
/* On the gradient everything is white: the global heading colour is a blue that
|
||||||
would vanish here. */
|
would vanish here. */
|
||||||
|
/* balance evens out the lines, so a short last word ("IGMH") never wraps alone. */
|
||||||
.hero h1 {
|
.hero h1 {
|
||||||
color: var(--color-on-header);
|
color: var(--color-on-header);
|
||||||
|
text-wrap: balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The intro is editor Markdown, rendered without scope attributes, so it is reached
|
/* The intro is editor Markdown, rendered without scope attributes, so it is reached
|
||||||
@@ -289,14 +291,23 @@
|
|||||||
align-self: start;
|
align-self: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* The teaser is a whole sentence; the tile cuts it visually after three lines,
|
||||||
|
so the tiles of a row keep a similar height whatever the editor wrote. The
|
||||||
|
-webkit- box is the form every current browser supports for line-clamp. */
|
||||||
.tile-text {
|
.tile-text {
|
||||||
display: block;
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 3;
|
||||||
|
line-clamp: 3;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* --- Embeds -------------------------------------------------------------------
|
/* --- Embeds -------------------------------------------------------------------
|
||||||
The blocks the home page opts into (posts, events) sit side by side from desktop
|
The blocks the home page opts into (posts, events) sit side by side from desktop
|
||||||
width, so the start page shows both without a long scroll. One column below. */
|
width, so the start page shows both without a long scroll. One column below.
|
||||||
|
In the grid the blocks' own top margin no longer collapses with the margin of
|
||||||
|
the tiles above, so it is dropped there; otherwise the gap would be doubled. */
|
||||||
@media (min-width: 64rem) {
|
@media (min-width: 64rem) {
|
||||||
.home-embeds {
|
.home-embeds {
|
||||||
display: grid;
|
display: grid;
|
||||||
@@ -304,4 +315,8 @@
|
|||||||
gap: var(--space-5);
|
gap: var(--space-5);
|
||||||
align-items: start;
|
align-items: start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.home-embeds ::deep .embed {
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -84,18 +84,12 @@ public static partial class Markdown
|
|||||||
/// short description on a home page tile.
|
/// short description on a home page tile.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="markdown">The editor's Markdown; may be <see langword="null"/>.</param>
|
/// <param name="markdown">The editor's Markdown; may be <see langword="null"/>.</param>
|
||||||
/// <param name="maxLength">
|
|
||||||
/// The longest sentence kept as is; a longer one is cut at the last word
|
|
||||||
/// boundary before the limit and ends in <c>…</c>.
|
|
||||||
/// </param>
|
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// The first sentence of the first top-level paragraph, with all Markdown
|
/// The first sentence of the first top-level paragraph, with all Markdown
|
||||||
/// removed and whitespace collapsed, or an empty string when there is no such
|
/// removed, whitespace collapsed and a trailing colon dropped, or an empty
|
||||||
/// paragraph (empty body, or only headings, lists and blocks).
|
/// string when there is no such paragraph (empty body, or only headings, lists
|
||||||
|
/// and blocks).
|
||||||
/// </returns>
|
/// </returns>
|
||||||
/// <exception cref="ArgumentOutOfRangeException">
|
|
||||||
/// <paramref name="maxLength"/> is zero or negative.
|
|
||||||
/// </exception>
|
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Only top-level paragraphs count: a heading repeats the title, and the text in
|
/// Only top-level paragraphs count: a heading repeats the title, and the text in
|
||||||
/// a list or a <c>:::</c> block is rarely a sentence that describes the page.
|
/// a list or a <c>:::</c> block is rarely a sentence that describes the page.
|
||||||
@@ -106,6 +100,12 @@ public static partial class Markdown
|
|||||||
/// longer abbreviation such as <c>bzw.</c> still does; that costs the rest
|
/// longer abbreviation such as <c>bzw.</c> still does; that costs the rest
|
||||||
/// of a teaser, never the page.
|
/// of a teaser, never the page.
|
||||||
/// </para>
|
/// </para>
|
||||||
|
/// <para>
|
||||||
|
/// A paragraph that leads into a list often ends in <c>:</c> without a
|
||||||
|
/// period; as a teaser the colon would point at a list that is not there.
|
||||||
|
/// The sentence is returned whole: a long one is cut by the tile's CSS,
|
||||||
|
/// which knows the space it has, not by a character count.
|
||||||
|
/// </para>
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
/// <example>
|
/// <example>
|
||||||
/// <code>
|
/// <code>
|
||||||
@@ -113,13 +113,11 @@ public static partial class Markdown
|
|||||||
/// // "Wir sind sieben Eltern."
|
/// // "Wir sind sieben Eltern."
|
||||||
/// </code>
|
/// </code>
|
||||||
/// </example>
|
/// </example>
|
||||||
public static string FirstSentence(string? markdown, int maxLength = 140) =>
|
public static string FirstSentence(string? markdown) =>
|
||||||
maxLength <= 0
|
string.IsNullOrWhiteSpace(markdown)
|
||||||
? throw new ArgumentOutOfRangeException(nameof(maxLength), maxLength, "Must be positive.")
|
|
||||||
: string.IsNullOrWhiteSpace(markdown)
|
|
||||||
? ""
|
? ""
|
||||||
: Parse(markdown).OfType<ParagraphBlock>().FirstOrDefault()?.Inline is { } inline
|
: Parse(markdown).OfType<ParagraphBlock>().FirstOrDefault()?.Inline is { } inline
|
||||||
? Shorten(UpToSentenceEnd(PlainText(inline)), maxLength)
|
? UpToSentenceEnd(PlainText(inline)).TrimEnd(':', ' ')
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -194,25 +192,6 @@ public static partial class Markdown
|
|||||||
private static string UpToSentenceEnd(string text) =>
|
private static string UpToSentenceEnd(string text) =>
|
||||||
SentenceEnd().Match(text) is { Success: true } end ? text[..(end.Index + 1)] : text;
|
SentenceEnd().Match(text) is { Success: true } end ? text[..(end.Index + 1)] : text;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Shortens a text to at most <paramref name="maxLength"/> characters plus an
|
|
||||||
/// ellipsis, cutting between words.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="text">Plain text.</param>
|
|
||||||
/// <param name="maxLength">The longest text kept as is.</param>
|
|
||||||
/// <returns>The text, shortened if needed.</returns>
|
|
||||||
/// <remarks>
|
|
||||||
/// The search for a space starts at <paramref name="maxLength"/> itself, so a
|
|
||||||
/// word that ends exactly at the limit is kept. A single word longer than the
|
|
||||||
/// limit is cut hard.
|
|
||||||
/// </remarks>
|
|
||||||
private static string Shorten(string text, int maxLength) =>
|
|
||||||
text.Length <= maxLength
|
|
||||||
? text
|
|
||||||
: text.LastIndexOf(' ', maxLength) is > 0 and var space
|
|
||||||
? $"{text[..space].TrimEnd(',', ';', ':', '-', '–')}…"
|
|
||||||
: $"{text[..maxLength]}…";
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Matches the end mark of a sentence: <c>.</c>, <c>!</c> or <c>?</c> before a
|
/// Matches the end mark of a sentence: <c>.</c>, <c>!</c> or <c>?</c> before a
|
||||||
/// space or the end, unless it follows a lone letter (<c>z.</c>) or a number
|
/// space or the end, unless it follows a lone letter (<c>z.</c>) or a number
|
||||||
|
|||||||
+2
-1
@@ -131,7 +131,8 @@ Darunter erscheinen automatisch:
|
|||||||
keinen, entfällt die Karte.
|
keinen, entfällt die Karte.
|
||||||
- **Kacheln** — eine pro öffentlicher Seite mit `location = header` (außer der
|
- **Kacheln** — eine pro öffentlicher Seite mit `location = header` (außer der
|
||||||
Startseite), in Menü-Reihenfolge. Der Kacheltext ist der **erste Satz** des
|
Startseite), in Menü-Reihenfolge. Der Kacheltext ist der **erste Satz** des
|
||||||
`body` (ohne Überschrift, ggf. gekürzt); beginnt der Text mit einer Liste oder
|
`body` (ohne Überschrift, ein Doppelpunkt am Ende fällt weg; ein langer Satz
|
||||||
|
wird nach drei Zeilen mit „…“ abgeschnitten); beginnt der Text mit einer Liste oder
|
||||||
einem Baustein, zeigt die Kachel nur den Titel. Auf dem Handy zeigen die
|
einem Baustein, zeigt die Kachel nur den Titel. Auf dem Handy zeigen die
|
||||||
Kacheln immer nur Symbol und Titel (zwei Spalten), ab Tablet-Breite auch den
|
Kacheln immer nur Symbol und Titel (zwei Spalten), ab Tablet-Breite auch den
|
||||||
Text (drei Spalten). Das Symbol hängt am Slug und ist
|
Text (drei Spalten). Das Symbol hängt am Slug und ist
|
||||||
|
|||||||
Reference in new issue
Block a user