Compare commits

..

3 commits

11 changed files with 185 additions and 178 deletions

View file

@ -50,7 +50,7 @@ $(BUILDDIR)/%: $(TMPDIR)/%
$(TMPDIR)/%.js: %.ts
echo "[tsc] "$@
tsc --strict --noEmitOnError \
tsc --strict --noUncheckedIndexedAccess --noEmitOnError \
--lib dom,es2021 --target es2015 \
--outDir $(dir $@) $^

View file

@ -34,7 +34,7 @@ make' root (GalleryInfo {title, desc, prefix, filters, hidden}) infos = [b|@0
<meta property=og:title content="$title">
<meta property=og:site_name content="$title">
<meta property=og:description content="$desc">
<meta property=og:image content="$url/$imagepath0">
<meta property=og:image content="$url/style/card.png">
<meta property=og:url content="$url">
<meta name=twitter:site content=@2_gecs>
<meta name=twitter:card content=summary>
@ -108,9 +108,6 @@ make' root (GalleryInfo {title, desc, prefix, filters, hidden}) infos = [b|@0
nsfw = filters.nsfw /= NoNsfw
url = [b|$root/$prefix|]
imagepath0
| (_, (p, i) : _) : _ <- infosByYear = getThumb (takeDirectory p) i
| otherwise = "/style/card.png"
nsfw' = NsfwWarning.Gallery <$ guard nsfw
nsfwScript = NsfwWarning.script nsfw'

View file

@ -13,6 +13,7 @@ module Info
CompareKey (..), compareKeyFor, compareFor, sortFor,
Artist (..), Images' (..), Images, Image (..), Desc (..), DescField (..),
PreviewImage (..), previewImage,
Link (..), Update (..), Bg (..),
@ -117,6 +118,13 @@ data Images' a =
type Images = Images' Image
data PreviewImage = PFull Image | PThumb FilePath
previewImage :: Info -> Maybe PreviewImage
previewImage info
| Just img <- find (.sfw) $ allImages info.images = Just $ PFull img
| otherwise = PThumb <$> info.thumb'
data Link =
Link {
@ -261,8 +269,7 @@ newtype NoThumb = NoThumb FilePath
instance Show NoThumb where show (NoThumb dir) = "no thumbnail for " ++ dir
getThumb :: FilePath -> Info -> FilePath
getThumb dir =
maybe (throw $ NoThumb dir) (\t -> dir </> thumbFile t) . thumb
getThumb dir = maybe (throw $ NoThumb dir) (\t -> dir </> thumbFile t) . thumb
thumbFile :: FilePath -> FilePath
thumbFile = addSuffix "_small"

View file

@ -8,6 +8,7 @@ import Data.List (intersperse)
import qualified Data.List as List
import Data.Text.Lazy (Text)
import Data.Text.Lazy.Builder (toLazyText)
import qualified Data.Text as Strict
import qualified Data.Text.Lazy.IO as Text
import qualified Data.YAML as YAML
import System.FilePath (makeRelative, takeDirectory, takeFileName)
@ -56,7 +57,7 @@ main2 (SinglePage {root, file, prefix, index, dataDir, nsfw, output}) = do
writeOutput output page
main2 (GalleryPage {root, files, prefix, index, output, dataDir}) = do
ginfo <- galleryFromIndex index prefix
(_, ginfo) <- galleryFromIndex index prefix
printV $ "gallery_info" := ginfo
infos <- mapM (infoYAML dataDir) files
printV $ "infos" := infos
@ -70,12 +71,12 @@ main2 (IndexPage {root, file, output}) = do
writeOutput output page
main2 (RSS {files, root, index, prefix, output, dataDir}) = do
ginfo <- galleryFromIndex index prefix
(name, ginfo) <- galleryFromIndex index prefix
printV $ "gallery_info" := ginfo
infos <- mapM (infoYAML dataDir) files
printV $ "infos" := infos
let output' = takeFileName <$> output
let rss = RSS.make root ginfo output' infos
let rss = RSS.make root name ginfo output' infos
writeOutput output rss
main2 (DependSingle {index, file, nsfw, output, prefix, buildDir, dataDir}) = do
@ -117,10 +118,10 @@ findInfos dataDir infoName =
readYAML :: YAML.FromYAML a => FilePath -> IO a
readYAML file = ByteString.readFile file >>= decode1Must file
galleryFromIndex :: FilePath -> FilePath -> IO GalleryInfo
galleryFromIndex :: FilePath -> FilePath -> IO (Strict.Text, GalleryInfo)
galleryFromIndex file prefix = do
IndexInfo {galleries} <- readYAML file
maybe (fail $ "no gallery with prefix " ++ prefix) pure $
IndexInfo {title, galleries} <- readYAML file
maybe (fail $ "no gallery with prefix " ++ prefix) (pure . (title,)) $
List.find (\g -> g.prefix == prefix) galleries
decode1Must :: YAML.FromYAML a => FilePath -> ByteString -> IO a

View file

@ -17,27 +17,15 @@ script (Just _) = [b|<script src=/script/nsfw-warning.js type=module></script>|]
dialog :: Maybe What -> Builder
dialog Nothing = ""
dialog (Just what) = [b|@0
<div class=dialog id=nsfw-dialog>
<div class=dialog-inner>
<dialog id=nsfw-dialog>
<h1>cw: lewd art</h1>
<img class=dialog-icon src=/style/stop_hand.svg>
<div class=dialog-message>
<p>
$what contains pornographic content that is
<strong>not suitable for minors</strong>.
<p>
by continuing, you are confirming that you are at least
<strong>eighteen years old</strong>.
</div>
<div class=dialog-buttons>
<img src=/style/stop_hand.svg>
<div> you must be an adult to view $what. no minors! </div>
<form>
<button id=nsfw-yes class=yes>i am an adult</button>
<a href=//crouton.net referrerpolicy=no-referrer>
<button id=nsfw-no class=no>i am not</button>
</a>
</div>
</div>
</div>
</form>
</dialog>
|]

View file

@ -4,8 +4,8 @@ import Date
import Info
import BuilderQQ
import Data.List (sortBy)
import Data.Maybe (isJust)
import Data.List (sortBy, intersperse)
import Data.Maybe (catMaybes)
import Data.Function (on)
import qualified Data.Text as Strict
import qualified Data.Text.Lazy as Lazy
@ -13,20 +13,21 @@ import System.FilePath (takeDirectory)
make :: Strict.Text -- ^ website root e.g. @https://gallery.niss.website@
-> Strict.Text -- ^ website name e.g. @nissart@
-> GalleryInfo
-> Maybe FilePath -- ^ output filename for self link
-> [(FilePath, Info)]
-> Lazy.Text
make root ginfo output infos =
toLazyText $ make' root ginfo output infos
make root name ginfo output infos =
toLazyText $ make' root name ginfo output infos
make' :: Strict.Text -> GalleryInfo
make' :: Strict.Text -> Strict.Text -> GalleryInfo
-> Maybe FilePath -> [(FilePath, Info)] -> Builder
make' root ginfo@(GalleryInfo {title, desc, prefix}) output infos = [b|@0
make' root name ginfo@(GalleryInfo {title, desc, prefix}) output infos = [b|@0
<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
<rss version="2.0">
<channel>
<title>$title</title>
<title>$name$title</title>
<link>$link</link>
<description>$desc</description>
$selflink
@ -43,37 +44,54 @@ make' root ginfo@(GalleryInfo {title, desc, prefix}) output infos = [b|@0
filter (not . (.unlisted) . snd) infos
selflink = case output of
Nothing -> ""
Just o -> [b|<atom:link href="$link/$o" rel="self" />|]
Just o -> [b|<link href="$link/$o" rel="self" />|]
makeItem :: Strict.Text -> FilePath -> Bool -> FilePath -> Info -> Builder
makeItem root prefix nsfw path i@(Info {title, artist}) = [b|@4
<item>
<title>$title$up</title>
<title>$title$suf</title>
<link>$link</link>
<guid>$link</guid>
$descArtist'
$body
<pubDate>$date</pubDate>
</item>
|]
where
up = if hasUpdatesFor nsfw i then [b| (updated)|] else ""
suf = let parts = catMaybes [o18, cnt, up] in
if null parts then ""
else " (" <> mconcat (intersperse ", " parts) <> ")"
up = if hasUpdatesFor nsfw i then Just "updated" else Nothing
o18 = if nsfw && anyNsfw i then Just "🔞" else Nothing
cnt = let len = maybe 0 length $ allImages <$> imagesFor nsfw i in
if len == 1 then Nothing else Just [b|$len images|]
dir = takeDirectory path
link = [b|$root/$prefix/$dir|]
date = formatRSS $ latestDateFor nsfw i
artist' = ifJust artist \case
Artist {name, url = Nothing} -> [b|<p>by $name|]
Artist {name, url = Just url} -> [b|<p>by <a href=$url>$name</a>|]
Artist {name, url = Just url} -> [b|<p>by <a href="$url">$name</a>|]
desc = descFor nsfw i
desc' = makeDesc desc
descArtist' = if desc.exists || isJust artist then [b|@6
<description>
<![CDATA[
$10.artist'
$10.desc'
]]>
</description>
body = [b|@6
<description> <![CDATA[
$8.image
$8.artist'
$8.desc'
]]> </description>
|]
image = case previewImage i of
Just (PFull img) -> go $ pageFile img
Just (PThumb th) -> go $ thumbFile th
Nothing -> ""
where go p = [b|@0
<figure>
<a href="$link"><img src="$link/$p"></a>
</figure>
|]
else ""
date = formatRSS $ latestDateFor nsfw i
makeDesc :: Desc -> Builder
makeDesc NoDesc = ""

View file

@ -9,7 +9,7 @@ import qualified NsfwWarning
import Control.Exception
import Control.Monad
import Data.List (sort, intersperse)
import Data.Maybe (fromMaybe, isNothing, isJust)
import Data.Maybe (fromMaybe, isJust)
import qualified Data.Text as Strict
import qualified Data.Text.Lazy as Lazy
import System.FilePath (joinPath, splitPath)
@ -94,7 +94,6 @@ make' root siteName prefix nsfw _dataDir dir
let desc = case artist of
Just (Artist {name}) -> [b|by $name|]
Nothing -> "by niss"
let thumbnail = getThumb "" info
let updateDate = ifJust (last' updates) \(d, _) ->
let updated = formatLong d in
@ -104,15 +103,17 @@ make' root siteName prefix nsfw _dataDir dir
let nsfwScript = NsfwWarning.script nsfw'
let nsfwDialog = NsfwWarning.dialog nsfw'
let imageMeta =
if image0.sfw && isNothing image0.warning then [b|@0
<meta property=og:image content="$url/$path0'">
let imageMeta = case previewImage info of
Just (PFull (Image {path})) -> [b|@0
<meta property=og:image content="$url/$path">
<meta name=twitter:card content=summary_large_image>
<meta name=twitter:image content="$url/$path0'">
|] else [b|@0
<meta property=og:image content="$url/$thumbnail">
<meta name=twitter:image content="$url/$path">
|]
Just (PThumb path) -> [b|@0
<meta property=og:image content="$url/$path">
<meta name=twitter:card content=summary>
|]
Nothing -> throw $ NoThumb dir
pure [b|@0
<!DOCTYPE html>

View file

@ -106,23 +106,30 @@ 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')
};
function useFragment() {
const frag = decodeURIComponent(location.hash).replace(/^#/, '');
const details = document.getElementById('filters-details') as HTMLDetailsElement;
const fromShortcut = shortcuts[frag];
if (!frag) {
clearForm();
} else if (frag == 'all') {
allBoxes.forEach(b => b.checked = false);
details.open = false;
} else if (fromShortcut) {
allBoxes.forEach(b => b.checked = fromShortcut.has(b.id));
} else {
const pieces =
frag == 'summary' ? ['require_artsummary'] :
frag == 'colourexamples' ? ['require_colourexample'] :
frag == 'flatexamples' ? ['require_flatexample'] :
frag == 'sketchexamples' ? ['require_sketchexample'] :
frag == 'iconexamples' ? ['require_iconexample'] :
frag == 'curated' ? ['require_curated'] : frag.split(';');
const pieces = frag.split(';');
const set = new Set(pieces);
const re = /^(require|exclude)_|hide_filters/;
if (pieces.every(x => re.test(x))) {
@ -154,7 +161,7 @@ function sortFilters(cmp: (a: Node, b: Node) => number) {
function sortFiltersAlpha(e: Event) {
function getName(node: Node): string {
if (node instanceof Element) {
return node.getElementsByTagName('input')[0].value;
return node.getElementsByTagName('input')[0]?.value ?? '';
} else {
return '';
}
@ -164,9 +171,9 @@ function sortFiltersAlpha(e: Event) {
}
function sortFiltersUses(e: Event) {
function getUses(node: Node) {
function getUses(node: Node): number {
if (node instanceof Element) {
const countStr = node.getElementsByTagName('label')[0].dataset.count;
const countStr = node.getElementsByTagName('label')[0]?.dataset.count;
return countStr ? +countStr : 0;
} else {
return 0;

View file

@ -1,21 +1,17 @@
const nsfwOk = 'nsfw-ok';
function dismiss() {
const dialog = document.getElementById('nsfw-dialog')!;
dialog.parentElement!.removeChild(dialog);
}
const dialog = document.getElementById('nsfw-dialog')! as HTMLDialogElement;
function yes() {
localStorage.setItem(nsfwOk, '1');
dismiss();
dialog.close();
}
function setup() {
if (localStorage.getItem(nsfwOk)) {
dismiss();
} else {
document.getElementById('nsfw-yes')!.onclick = yes;
if (!localStorage.getItem(nsfwOk)) {
(dialog.querySelector('#nsfw-yes') as HTMLElement).onclick = yes;
// nsfw-no is a normal link
dialog.showModal();
}
}

View file

@ -1,30 +1,22 @@
.dialog {
position: fixed;
top: 0; left: 0;
width: 100vw; height: 100vh;
margin: 0;
z-index: 1000;
dialog {
&[open] {
&::backdrop {
background: hsl(340deg, 35%, 15%, 90%);
color: var(--text-col);
display: grid;
}
mix-blend-mode: multiply;
}
~ .page { filter: blur(15px); }
.dialog ~ .page {
filter: blur(15px);
}
.dialog-inner {
place-self: center;
display: grid;
grid-template:
"header header"
"icon header"
"icon text"
"buttons buttons"
"icon buttons"
/ 1fr 3fr;
grid-gap: 0.5em;
align-items: center;
gap: 0.5em 1.5em;
place-items: center;
place-content: center;
min-height: 20vh;
max-width: 30em;
@ -33,42 +25,40 @@
background: var(--background);
border: var(--border);
border-radius: var(--border-radius);
}
box-shadow: 0 0 50px #fff6;
}
.dialog h1 {
color: var(--text-col);
h1 {
margin: 0;
grid-area: header;
justify-self: center;
}
}
.dialog-icon {
height: 3em;
width: 3em;
img {
grid-area: icon;
justify-self: center;
}
}
.dialog-message {
div {
grid-area: text;
justify-self: start;
text-align: center;
font-size: 125%;
}
}
.dialog-buttons {
form {
grid-area: buttons;
justify-self: center;
display: flex;
justify-content: center;
margin-top: 1em;
}
.dialog-buttons > * {
> * {
margin: 0 1em;
}
}
}
.dialog button {
button {
border: none;
border-radius: 0.75em;
padding: 0.5em 1em;
@ -81,30 +71,31 @@
position: relative;
cursor: pointer;
}
.dialog button a {
a {
inset: 0;
width: max-content;
text-decoration: none;
}
}
}
.dialog .yes {
.yes {
background: hsl(100deg, 70%, 80%);
}
}
.dialog .no {
.no {
background: hsl(5deg, 70%, 80%);
}
}
.dialog p {
p {
text-align: left;
-ms-hyphens: none;
hyphens: none;
}
}
.dialog strong {
strong {
font-weight: 800;
}
}
@media (pointer: coarse) {

View file

@ -1,6 +1,7 @@
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noEmitOnError": true,
"lib": ["ES2021", "dom"],
"target": "ES2015"