Compare commits

...

14 commits

10 changed files with 112 additions and 81 deletions

View file

@ -16,18 +16,11 @@ MAKEPAGES := $(TMPDIR)/make-pages
YAMLS != find -L $(DATADIR) -name $(INFONAME)
TSCRIPTS != find script -name '*.ts'
STYLES != find style -name '*.css'
STYLESVGS != find style -name '*.svg'
STYLEPNGS != find style -name '*.png'
TSCRIPTS != find script -name '*.ts'
STYLES != find style -type f
FONTS != find fonts -type f
FONTS != find fonts \
-iname '*.eot' -or -iname '*.svg' -or \
-iname '*.ttf' -or -iname '*.woff' -or \
-iname '*.woff2' -or -iname '*.css'
STATIC = $(STYLES) $(STYLEPNGS) $(STYLESVGS) $(FONTS)
STATIC = $(STYLES) $(FONTS)
BSTATIC = $(patsubst %,$(BUILDDIR)/%,$(STATIC))
BSCRIPTS = $(patsubst %.ts,$(BUILDDIR)/%.js,$(TSCRIPTS))

View file

@ -18,9 +18,15 @@ import Data.List.NonEmpty (NonEmpty, toList)
data Chunk = Lit String | Var String
-- |
-- * use @$var@ to insert a variable (instance of 'CanBuild')
-- * use @$&@ to insert nothing like @\&@ in a string (e.g. to add whitespace
-- at the start or end, or to have a variable followed by a letter
-- * use @$$@ for a literal @$@
parseB :: String -> ExpQ
parseB = toExpQ . reverse . go "" [] . trim where
trim = dropWhileEnd isSpace . dropWhile (== '\n')
trim = dropWhileEnd isSpace . dropWhile isSpace
go acc cs [] = addLit acc cs
go acc cs ('$':'&':rest) = go acc cs rest -- $&: expands to nothing
go acc cs ('$':'$':rest) = go ('$' : acc) cs rest -- $$: expands to one $
@ -58,7 +64,8 @@ escId = foldMap esc1 . Strict.unpack where
escAttr :: Strict.Text -> Builder
escAttr = foldMap esc1 . Strict.unpack where
esc1 c = fromMaybe (singleton c) $ lookup c
[('<', "&lt;"), ('>', "&gt;"), ('"', "&quot;"), ('\'', "&apos;")]
[('<', "&lt;"), ('>', "&gt;"), ('"', "&quot;"), ('\'', "&apos;"),
('&', "&amp;")]
class CanBuild a where build :: a -> Builder

View file

@ -80,9 +80,7 @@ make' root (GalleryInfo {title, desc, prefix, filters, hidden}) infos = [b|
</nav>
<main>
<ul class=grid>
$items
</ul>
$items
</main>
</div>
|]
@ -129,16 +127,17 @@ makeFilter prefix initial tag count = [b|
where
id' = [b|$prefix$&_$tag'|]
tag' = escId tag
checked = if HashSet.member tag initial then [b| checked|] else ""
hidden = if count <= 1 then [b| hidden|] else ""
checked = if HashSet.member tag initial then [b|$& checked|] else ""
hidden = if count <= 1 then [b|$& hidden|] else ""
makeYearItems :: Bool -- ^ nsfw
-> Int -- ^ year
-> [(FilePath, Info)]
-> Builder
makeYearItems nsfw year infos = [b|
<li class="item year-marker" id="marker-$year">
<span class=year-text>$year'</span>
<h2 class="item year-marker" id="marker-$year" aria-label=$year>
$year'
</h2>
$items
|]
where
@ -147,24 +146,25 @@ makeYearItems nsfw year infos = [b|
makeItem :: Bool -> FilePath -> Info -> Builder
makeItem nsfw file info@(Info {bg}) = [b|
<li class="item post$nsfw'" data-year=$year' data-updated="$updated'"
<figure class="item post$nsfw'" data-year=$year' data-updated="$updated'"
data-tags="$tags'">
<a href="$dir">
<img src="$thumbnail" loading=lazy$bgStyle
width=200 height=200
title="$tooltip">
alt="$title" title="$tooltip">
</a>
</figure>
|]
where
title = fromMaybe info.title $ info.galleryTitle
title = escAttr $ fromMaybe info.title info.galleryTitle
dir = takeDirectory file
thumbnail = getThumb dir info
nsfw' = if nsfw && anyNsfw info then [b| nsfw|] else ""
nsfw' = if nsfw && anyNsfw info then [b|$& nsfw|] else ""
tags' = fold $ intersperse ";" $ map fromText $ tagsFor nsfw info
date = latestDateFor nsfw info
date' = formatTooltip date
year' = date.year
updated' = if hasUpdatesFor nsfw info then [b|true|] else [b|false|]
bgStyle = case bg of Other col -> [b| style="background: $col"|]; _ -> ""
bgStyle = case bg of Other col -> [b|$& style="background: $col"|]; _ -> ""
tooltip = [b|$title ($upd$date')|]
where upd = if hasUpdatesFor nsfw info then "updated " else "" :: Builder

View file

@ -74,13 +74,13 @@ makeItem :: GalleryInfo -> Builder
makeItem (GalleryInfo {title, desc, prefix, filters}) = [b|
<li$nsfw><a href=$prefix title="$desc">$title</a></li>
|]
where nsfw = if hasNsfw filters then [b| class=nsfw|] else ""
where nsfw = if hasNsfw filters then [b|$& class=nsfw|] else ""
makeLink :: Link -> Builder
makeLink (Link {title, url, nsfw}) = [b|
<li$nsfw'><a href=$url>$title</a>
|]
where nsfw' = if nsfw then [b| class=nsfw|] else ""
where nsfw' = if nsfw then [b|$& class=nsfw|] else ""
hasNsfw :: GalleryFilters -> Bool
hasNsfw (GalleryFilters {nsfw}) = nsfw /= NoNsfw

View file

@ -105,6 +105,7 @@ data Image =
label :: !Text,
path :: !FilePath,
download :: !(Maybe FilePath),
desc :: !Text,
nsfw :: !Bool,
warning :: !(Maybe Text),
resize :: !Bool
@ -198,7 +199,7 @@ thumb (Info {thumb', images}) =
latestDateFor :: Bool -> Info -> Date
latestDateFor nsfw i = maximum $ i.date : mapMaybe relDate (updatesFor nsfw i)
where
relDate (date, us) = date <$ guard (not $ null us || any (.ignoreSort) us)
relDate (date, us) = date <$ guard (not $ null us || all (.ignoreSort) us)
latestYearFor :: Bool -> Info -> Int
latestYearFor nsfw info = (latestDateFor nsfw info).year
@ -369,17 +370,18 @@ unlabelledImage' label' y = asStr y <|> asObj y
where
asStr = YAML.withStr "path" \(Text.unpack -> path) ->
let label = fromMaybe (pathToLabel path) label' in
pure $ Image {label, path, download = Nothing,
pure $ Image {label, path, download = Nothing, desc = "",
nsfw = False, warning = Nothing, resize = True}
asObj = YAML.withMap "image info" \m -> do
checkKeys m ["path", "download", "nsfw", "warning", "resize"]
checkKeys m ["path", "download", "desc", "nsfw", "warning", "resize"]
path <- m .: "path"
download <- m .:? "download"
desc <- m .:? "desc" .!= ""
nsfw <- m .:? "nsfw" .!= False
warning <- m .:? "warning"
resize <- m .:? "resize" .!= True
let label = fromMaybe (pathToLabel path) label'
pure $ Image {label, path, download, nsfw, warning, resize}
pure $ Image {label, path, download, nsfw, warning, desc, resize}
pathToLabel = Text.pack . gapToSpace . takeBaseName
gapToSpace = map \case '-' -> ' '; '_' -> ' '; c -> c
@ -407,14 +409,13 @@ instance FromYAML Link where
updateList :: Maybe (YAML.Node YAML.Pos) ->
YAML.Parser [(Date, NonEmpty Update)]
updateList =
maybe (pure []) $ YAML.withMap "updates" $
nonEmptys <=< traverse bodies . Map.toList
maybe (pure []) $ YAML.withMap "updates" $ traverse bodies . Map.toList
where
bodies (date', rest) = (,) <$> parseYAML date' <*> body rest
body b =
return <$> body1 b
<|> YAML.withSeq "update list" (traverse body1) b
body b = return <$> body1 b <|> YAML.withSeq "update list" (bodyN b) b
body1 b = asDesc b <|> asObj b
bodyN y =
maybe (YAML.typeMismatch "non-empty list" y) (traverse body1) . nonEmpty
asDesc = YAML.withStr "desc" \desc ->
pure $ Update {desc, nsfw = False, ignoreSort = False}
asObj = YAML.withMap "update info" \m -> do
@ -423,8 +424,6 @@ updateList =
nsfw <- m .:? "nsfw" .!= False
ignoreSort <- m .:? "ignore-sort" .!= False
pure $ Update {desc, nsfw, ignoreSort}
nonEmptys = traverse $ traverse $
maybe (fail "expected non-empty list") pure . nonEmpty
data GalleryInfo =

View file

@ -26,11 +26,12 @@ make' :: Strict.Text -> Strict.Text -> GalleryInfo
-> Maybe FilePath -> [(FilePath, Info)] -> Builder
make' root name ginfo@(GalleryInfo {title, desc, prefix}) output infos = [b|
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<channel>
<title>$name$title</title>
<title><![CDATA[$name$title]]></title>
<link>$link</link>
<description>$desc</description>
<description><![CDATA[$desc]]></description>
$selflink
$items
@ -50,11 +51,12 @@ make' root name ginfo@(GalleryInfo {title, desc, prefix}) output infos = [b|
makeItem :: Strict.Text -> FilePath -> Bool -> FilePath -> Info -> Builder
makeItem root prefix nsfw path info@(Info {title}) = [b|
<item>
<title>$title$suffix</title>
<title><![CDATA[$title$suffix]]></title>
<link>$link</link>
<guid>$link</guid>
$body
<dc:creator><![CDATA[$creator]]></dc:creator>
<pubDate>$date</pubDate>
$body
</item>
|]
where
@ -77,20 +79,37 @@ makeItem root prefix nsfw path info@(Info {title}) = [b|
dir = takeDirectory path
link = [b|$root/$prefix/$dir|]
creator = maybe "niss" (.name) info.artist
date = formatRSS $ latestDateFor nsfw info
artist = ifJust info.artist \case
Artist name Nothing -> [b|<p>by $name|]
Artist name (Just url) -> [b|<p>by <a href="$url">$name</a>|]
desc = makeDesc $ descFor nsfw info
image = case previewImage info of
Just (PFull img) -> figure $ pageFile img
Just (PThumb th) -> figure $ thumbFile th
preview = previewImage info
image = case preview of
Just (PFull img) -> figure (escAttr img.desc) $ pageFile img
Just (PThumb th) -> figure "full image hidden" $ thumbFile th
Nothing -> ""
figure p = [b|<figure> <a href="$link"><img src="$link/$p"></a> </figure>|]
msg = case preview of
Just (PThumb _) -> "<p>(full image hidden; open to see)</p>"
_ -> "" :: Text
figure alt p = [b|
<figure aria-describedby=mainimg>
<a href="$link">
<img id=mainimg src="$link/$p" alt="$alt" title="$alt">
</a>
</figure>
$msg
|]
makeDesc :: Desc -> Builder
makeDesc NoDesc = ""
makeDesc (TextDesc txt) = [b|$txt|]
makeDesc (LongDesc fs) = [b|<dl>$fields</dl>|]
where fields = map (\(DescField {name, text}) -> [b|<dt>$name <dd>$text|]) fs
makeDesc (LongDesc fs) = [b|<ul>$fields</ul>|] where
fields = map mkField fs
mkField (DescField {name, text}) = [b|
<li> <b>$name</b>:
$text
|]

View file

@ -17,6 +17,7 @@ import qualified Data.HashSet as Set
import Data.Traversable
import Data.Semigroup
import Data.List.NonEmpty (toList)
import Data.Char (isSpace)
-- | e.g. only nsfw images are present for a non-nsfw page
@ -95,6 +96,8 @@ make' root siteName prefix nsfw _dataDir dir
Just (Artist {name}) -> [b|by $name|]
Nothing -> "by niss"
let alt = escAttr image0.desc
let updateDate = ifJust (last' updates) \(d, _) ->
let updated = formatLong d in
[b|<br> <span class=updated>updated $updated</span>|]
@ -104,23 +107,25 @@ make' root siteName prefix nsfw _dataDir dir
let nsfwDialog = NsfwWarning.dialog nsfw'
let imageMeta = case previewImage info of
Just (PFull (Image {path})) -> [b|
Just (PFull (pageFile -> path)) -> [b|
<meta property=og:image content="$url/$path">
<meta name=twitter:card content=summary_large_image>
<meta name=twitter:image content="$url/$path">
|]
Just (PThumb path) -> [b|
Just (PThumb (thumbFile -> path)) -> [b|
<meta property=og:image content="$url/$path">
<meta name=twitter:card content=summary>
|]
Nothing -> throw $ NoThumb dir
let escTitle = escAttr title
pure [b|
<!DOCTYPE html>
<html lang=en>
<meta charset=utf-8>
<meta name=viewport content="width=1200,viewport-fit=cover">
<link rel=stylesheet href=/style/shiny/single.css>
<link rel=stylesheet href=/style/shiny/single.css blocking=render>
<link rel=icon href=/style/niss.svg>
<meta property=og:type content=article>
@ -138,13 +143,13 @@ make' root siteName prefix nsfw _dataDir dir
$prefetches
<title>$title</title>
<title>$escTitle</title>
$nsfwDialog
<div class=page>
<header>
<h1>$title</h1>
<h1>$escTitle</h1>
<h2 id=date class="right corner">
$formattedDate $updateDate
</h2>
@ -156,10 +161,10 @@ make' root siteName prefix nsfw _dataDir dir
$buttonBar
<main>
<figure id=mainfig>
<figure id=mainfig aria-labelledby=mainimg>
$warning'
<a id=mainlink href="$download0" title="download full version">
<img id=mainimg src="$path0'" alt="">
<img id=mainimg src="$path0'" alt="$alt" title="$alt">
</a>
</figure>
@ -297,16 +302,17 @@ altButton :: Image -> Text -> Builder
altButton img i = [b|
<li$nsfwClass>
<input type=radio name=variant id="$i" value="$path'"
data-link="$link"$warning'>
data-link="$link"$warning' data-alt="$alt">
<label for="$i"$nsfwLabelClass>$label</label>
|]
where
Image {label, nsfw, warning, download} = img
nsfwClass = if nsfw then [b| class=nsfw|] else ""
nsfwLabelClass = if nsfw then [b| class=nsfw-label|] else ""
nsfwClass = if nsfw then [b|$& class=nsfw|] else ""
nsfwLabelClass = if nsfw then [b|$& class=nsfw-label|] else ""
path' = pageFile img
link = fromMaybe (bigFile img) download
warning' = ifJust warning \(escAttr -> w) -> [b| data-warning="$w"|]
warning' = ifJust warning \(escAttr -> w) -> [b|$& data-warning="$w"|]
alt = img.desc
makeTags :: FilePath -> [Strict.Text] -> Builder
makeTags undir tags =
@ -361,4 +367,8 @@ makeUpdate date ups = [b|
<dd>$desc
|] where
date' = formatSlash date
desc = mconcat $ map fromText $ intersperse "; " $ toList $ fmap (.desc) ups
desc = mconcat $
intersperse "; " $
map (fromText . Strict.dropWhileEnd isSpace) $
toList $
fmap (.desc) ups

View file

@ -106,14 +106,18 @@ function makeFragment() {
}
}
type Shortcuts = { [short: string]: Set<string> };
const shortcuts: Shortcuts = {
'summary': new Set('require_artsummary'),
'colourexamples': new Set('require_colourexample'),
'flatexamples': new Set('require_flatexample'),
'sketchexamples': new Set('require_sketchexample'),
'iconexamples': new Set('require_iconexample'),
'curated': new Set('require_curated')
summary: new Set(['require_artsummary']),
colourexamples: new Set(['require_colourexample']),
flatexamples: new Set(['require_flatexample']),
sketchexamples: new Set(['require_sketchexample']),
iconexamples: new Set(['require_iconexample']),
curated: new Set(['require_curated']),
gecs: new Set(['require_niss', 'require_nisse']),
qt: new Set(['require_q_t_']),
kesi: new Set(['require_kesi']),
};
function useFragment() {
@ -195,12 +199,11 @@ function setup() {
itemsByYear = new Map;
for (const item of items) {
const year = item.dataset.year;
if (year !== undefined) {
if (!itemsByYear.has(year)) {
itemsByYear.set(year, new Set([item]));
} else {
itemsByYear.get(year)?.add(item);
}
if (!year) continue;
if (!itemsByYear.has(year)) {
itemsByYear.set(year, new Set([item]));
} else {
itemsByYear.get(year)!.add(item);
}
}

View file

@ -21,7 +21,8 @@ function addCWListeners(id: string | null, caption: HTMLElement): void {
e => { if (e.key == 'Enter') openCW(id, caption, true) });
}
function setImage(id: string, src: string, href: string, cw: string): void {
function setImage(id: string, src: string, href: string,
alt: string, cw: string): void {
const curCw = document.getElementById('cw');
const coverNew = cw != '' && !opened.has(id) && !skipAll.checked;
@ -45,12 +46,14 @@ function setImage(id: string, src: string, href: string, cw: string): void {
// else no cover before or after
mainimg.src = src;
mainimg.alt = mainimg.title = alt;
mainlink.href = href;
}
function activateButton(button: HTMLInputElement, doPush = true): void {
setImage(button.id, button.value,
button.dataset.link!,
button.dataset.alt ?? '',
button.dataset.warning ?? '');
if (doPush) history.pushState(null, '', '#' + button.id);

View file

@ -154,7 +154,7 @@
}
.grid {
main {
padding: 0;
display: grid;
grid: auto-flow / repeat(auto-fit, var(--image-size));
@ -169,6 +169,7 @@
width: var(--image-size);
height: var(--image-size);
overflow: hidden;
margin: 0;
position: relative;
}
@ -185,17 +186,13 @@
box-shadow: var(--focus-box);
}
.year-marker {
.year-marker:not([hidden]) {
/* uncomment to reenable line breaks before year markers */
/* grid-area: auto / 1; */
padding: var(--border-thickness);
}
.year-text {
--gap: 0.2em;
display: grid;
grid-template-columns: repeat(2, calc(50% - 3 * var(--gap)));
grid-gap: var(--gap);
grid-gap: 0;
align-items: center;
justify-content: center;
height: 100%;