diff options
author | polwex <polwex@sortug.com> | 2025-06-27 22:53:52 +0700 |
---|---|---|
committer | polwex <polwex@sortug.com> | 2025-06-27 22:53:52 +0700 |
commit | 328ebe85135912678bdacd3381126ffd66ef2761 (patch) | |
tree | 365962bf45302f2a440f766a4f3c9e0a962dbe47 /desk/web/blog |
init
Diffstat (limited to 'desk/web/blog')
-rw-r--r-- | desk/web/blog/post-header.hoon | 118 | ||||
-rw-r--r-- | desk/web/blog/post-list.hoon | 159 | ||||
-rw-r--r-- | desk/web/blog/post.hoon | 344 | ||||
-rw-r--r-- | desk/web/blog/router.hoon | 198 | ||||
-rw-r--r-- | desk/web/blog/title.hoon | 11 |
5 files changed, 830 insertions, 0 deletions
diff --git a/desk/web/blog/post-header.hoon b/desk/web/blog/post-header.hoon new file mode 100644 index 0000000..3bee975 --- /dev/null +++ b/desk/web/blog/post-header.hoon @@ -0,0 +1,118 @@ +/- tp=trill-post +/+ sr=sortug, lib=boke, wall +=< html +|% +++ css ^~ %- trip +''' + #post-header{ + -webkit-locale: "en"; + text-size-adjust: 100%; + color: rgb(0, 0, 0); + font-size: 16px; + font-weight: 400; + text-rendering: optimizelegibility; + -webkit-font-smoothing: antialiased; + line-height: 1.618; + text-shadow: rgb(0, 0, 0) 0px 0px 0px; + align-items: center; + border-bottom-color: rgb(223, 223, 223); + border-bottom-style: dotted; + border-bottom-width: 1px; + box-sizing: border-box; + margin-left: 0px; + margin-right: 0px; + padding-bottom: 30px; + padding-left: 32px; + padding-right: 32px; + padding-top: 30px; + text-align: center; + + & h2{ + font-size: 2.1rem; + text-transform: uppercase; + } + + & #post-meta, .comment-meta, .entry-footer { + font-size: 90%; + font-style: italic; + color: #969696; + } + @media (min-width: 768px){ + & .col-md-4 { + -ms-flex: 0 0 33.333333%; + flex: 0 0 33.333333%; + max-width: 33.333333%; + } + } + & .breadcrumbs { + list-style: none; + display: inline-block; + margin: 0; + padding: 0; + border: none; + background: transparent; + text-indent: 0; + + } + & .breadcrumbs li { + display: inline-block; + margin: 0 0.3rem; + padding: 0; + border: none; + background: transparent; + text-indent: 0; + } + & .tags{ + display: block; + text-transform: capitalize; + } + + } +''' +++ html +|= [t=thread:tp who=@p] ^- manx +=/ url (trip (spat path.t)) :: TODO check for relative/absolute path +=/ date (date-to-tape:string:sr id.pid.t "-") + +=+ [[a y] m [d h mm s f]]=(yore id.pid.t) +=/ ys %- trip (ud-to-cord:string:sr y) +=/ ming (get-name:lib ship.pid.t) +=/ nam + ?. .=(ship.pid.t ~docteg-mothep) ming + "Spandrell" :: TODO should use the user component here + +=/ ttags (tags:wall ~(tap in tags.t)) +:: +;div#post-header + ;style:"{css}" + ;h2:"{(trip title.t)}" + ;div#post-meta + ;span + ; Posted by {nam} on + ;ul.breadcrumbs + ;li + ;a/"/{ys}":"{ys}" + == + ; / + ;li + ;a/"/{ys}/{<m.m>}":"{<m.m>}" + == + ; / + ;li + ;a/"/{ys}/{<m.m>}/{<d.d>}":"{<d.d>}" + == + == + == + ;+ ?: .=(0 (lent ttags)) ;span:"" + ;div.tags + ;* %+ mapi:sr ttags + |= [i=@ t=@t] ^- manx + =/ tt (trip t) + =/ path "/blog?t={tt}" + =/ show + ?. .=(i (dec (lent ttags))) "{tt}, " tt + ;a/"{path}":"{show}" + == + == +== +-- diff --git a/desk/web/blog/post-list.hoon b/desk/web/blog/post-list.hoon new file mode 100644 index 0000000..35a3590 --- /dev/null +++ b/desk/web/blog/post-list.hoon @@ -0,0 +1,159 @@ +/- tp=trill-post +/+ sr=sortug, plib=trill-utils, lib=boke, kaji +/= content /web/components/post-text +|% +++ abbreviated-post +|= [cm=content-map:tp button=manx] ^- (list manx) + =/ abbreviated (abbreviate-post:plib cm 1.000) + =/ blocks (latest-contents:plib cm) + ?: .=(blocks abbreviated) %+ turn blocks block:content + %+ snoc (turn abbreviated block:content) button + +++ post-snippet +|= t=thread:tp ^- manx + =/ url (trip (spat path.t)) :: TODO check for relative/absolute path + =+ [[a y] m [d h mm s f]]=(yore id.pid.t) + =/ ys %- trip (ud-to-cord:string:sr y) + :: =/ author (scot %p p.author.p) + =/ count (lent replies.t) + =/ comment-count ?: .=(count 0) "No" "{<count>}" + :: + =/ continue-button + ;div.continue-button-wrapper + ;a.continue-button/"{url}":"Continue" + == + =/ comment-div + ;span + ;a/"{url}#comments":"{comment-count} comments" + == + =/ tag-links + %+ mapi:sr ~(tap in tags.t) + |= [i=@ tg=@t] ^- manx + =/ tt (trip tg) + =/ path "/blog?t={tt}" + =/ show ?: .=(i (dec ~(wyt in tags.t))) tt "{tt}, " + ;a/"{path}":"{show}" + +:: +;article + ;header + ;h2 + ;a/"{url}":"{(trip title.t)}" + == + == + ;main + ;* %+ turn snip.t block:content + ;+ continue-button + == + ;div.post-metadata + ;span + ; Posted on + ;a/"{ys}":"{ys}" + ;a/"{ys}/{<m.m>}":"/{<m.m>}" + ;a/"{ys}/{<m.m>}/{<d.d>}":"/{<d.d>}" + == + :: ;span + :: ; by + :: ;a/"by/{author}":"{author}" + :: == + == + ;+ ?: .=(0 ~(wyt in tags.t)) ;footer ;+ comment-div == + ;footer + ; Tagged as + ;span.tags-links + ;* tag-links + == + ;+ comment-div + == +== +++ css ^~ %- trip +''' +#post-list{ + :: top: 0; + margin: auto; + + & article{ + margin: 0 0 1.5em; + margin-top: 40px; + border-bottom: 1px dotted #dfdfdf; + padding: 0 2rem; + padding-bottom: 40px; + + & header h2{ + font-size: 1.8rem; + text-align: center; + letter-spacing: 1px; + text-transform: uppercase; + line-height: 1.4em; + font-weight: 500; + } + & .post-metadata, article footer{ + font-size: 90%; + font-style: italic; + color: #969696; + } + + & main{ + margin: 1.5em 0 0; + } + } + & .tags-links{ + text-transform: capitalize; + margin-right: 1ch; + } + + & .continue-button-wrapper{ + margin-top: 1.5rem; + margin-bottom: 1.5rem; + } + & .continue-button{ + font-family: 'Montserrat', sans-serif; + text-transform: uppercase; + letter-spacing: 1px; + font-size: 13px; + -webkit-transition: all .4s ease; + -o-transition: all .4s ease; + transition: all .4s ease; + padding: 0.5rem, 0rem, 0.5rem, 1.25rem; + line-height: 1.5; + border-radius: 0.2rem; + } + & .cursor{ + text-align: center; + margin: 1rem; + } +} +@media (max-width: 800px){ + #post-list{ + & article{ + padding: 0; + line-height: 1.7rem; + } + } +} + + +''' +++ test +|= [p=post:tp children=full-graph:tp] ^- manx +=/ pid [author.p id.p] +=/ pat /blog/snip/(scot %uw (jam pid)) +(fetch:kaji pat) +++ cursor +|= [cursor=(unit @da) label=tape] +?~ cursor ;div; +=/ path "/blog/f?cursor={(scow:parsing:sr %uw u.cursor)}" +;div.cursor(kaji "iscroll", path path, cont "#post-list", where "bottom") + ;button:"{label} posts" +== +++ html +|= [p=tpage:tp =marl] +^- manx +;div#post-list.blog + ;style:"{css}" + ;* marl + :: ;+ (cursor newer.p "Newer") + ;* (turn p.p post-snippet) + ;+ (cursor older.p "Older") +== +-- diff --git a/desk/web/blog/post.hoon b/desk/web/blog/post.hoon new file mode 100644 index 0000000..0366fc9 --- /dev/null +++ b/desk/web/blog/post.hoon @@ -0,0 +1,344 @@ +/- tp=trill-post +/+ plib=trill-utils, sr=sortug, kaji, lib=boke, const=constants +/= post-text /web/components/post-text +/= post-header /web/blog/post-header +|_ [[t=thread:tp =post:tp children=full-graph:tp] =bowl:gall is-mobile=?] +++ css ^~ %- trip +''' +#post-text{ + -webkit-locale: "en"; + text-size-adjust: 100%; + color: rgb(0, 0, 0); + line-height: 1.618; + text-shadow: rgb(0, 0, 0) 0px 0px 0px; + border-bottom-color: rgb(223, 223, 223); + border-bottom-style: dotted; + border-bottom-width: 1px; + box-sizing: border-box; + display: block; + margin-bottom: 24px; + margin-left: 0px; + margin-right: 0px; + margin-top: 0px; + padding-bottom: 40px; + padding-left: 32px; + padding-right: 32px; + padding-top: 4px; +} +@media (max-width: 800px){ + #post-text{ + padding: 0; + line-height: 1.2; + } +} +#comments{ + -webkit-locale: "en"; + text-size-adjust: 100%; + color: rgb(0, 0, 0); + line-height: 1.618; + text-align: left; + text-shadow: rgb(0, 0, 0) 0px 0px 0px; + box-sizing: border-box; + display: block; + margin-bottom: 32px; + padding-bottom: 0px; + padding-left: 16px; + padding-right: 16px; + padding-top: 0px; +} +#comments h3{ + -webkit-locale: "en"; + text-size-adjust: 100%; + color: rgb(0, 0, 0); + text-align: left; + text-shadow: rgb(0, 0, 0) 0px 0px 0px; + font-size: 18.72px; + font-weight: 400; + box-sizing: border-box; + display: block; + line-height: 1.2; + margin-block-end: 9.36px; + margin-block-start: 9.36px; + margin-bottom: 9.36px; + margin-inline-end: 0px; + margin-inline-start: 0px; + margin-left: 0px; + margin-right: 0px; + margin-top: 9.36px; +} +.comment-list{ + -webkit-locale: "en"; + text-size-adjust: 100%; + color: rgb(0, 0, 0); + line-height: 1.618; + text-align: left; + text-shadow: rgb(0, 0, 0) 0px 0px 0px; + box-sizing: border-box; + display: block; + list-style-image: none; + list-style-position: outside; + list-style-type: none; + margin-block-end: 24px; + margin-block-start: 0px; + margin-bottom: 24px; + margin-inline-end: 0px; + margin-inline-start: 0px; + margin-left: 0px; + margin-right: 0px; + margin-top: 0px; + padding-inline-start: 0px; + padding-left: 0px; +} +.comment-list li{ + position: relative; + -webkit-locale: "en"; + text-size-adjust: 100%; + color: rgb(0, 0, 0); + line-height: 1.618; + text-shadow: rgb(0, 0, 0) 0px 0px 0px; + list-style-image: none; + list-style-position: outside; + list-style-type: none; + border-left-color: rgb(223, 223, 223); + border-left-style: solid; + border-left-width: 1px; + box-sizing: border-box; + display: list-item; + padding-bottom: 16px; + padding-left: 10px; + padding-right: 0px; + padding-top: 16px; + text-align: left; +} +.comment-author{ + font-weight: 700; + color: var(--text); + font-style: normal; + font-size: 1rem; + margin-right: 1rem; +} +.comment-content{ + font-size: 90%; +} +#reply-title{ + display: inline; +} +.reply-prompt{ + color: var(--hong); + font-size: 0.9rem; +} +.nested{ + padding-left: 1rem; +} +#reply-box{ + & textarea{ + width: 100%; + } + .author-data{ + display: flex; + justify-content: space-between; + + & .name{ + margin-left: 1rem; + } + } +} +/* desktop */ +@media (min-width: 800px){ + #reply-box{ + width: 40%; + } +} +/* mobile */ +@media (max-width: 800px){ + #reply-box{ + width: 100%; + } +} +.collapse-button, .uncollapse-button{ + width: 15px; + position: absolute; + left: -10px; + top: 21px; +} +''' +++ script ^~ %- trip +''' +function scanTweets(){ + const nodes = document.querySelectorAll(".parsed-twatter"); + for (let n of nodes){ + const status = n.getAttribute("status"); + twttr.widgets.createTweet(status, n.parentElement, { + conversation: true + }) + } +} +window.addEventListener('load', () => { + scanTweets(); +}) +''' +++ post-content +^- manx +;div#post-text + ;style:"{css}" + ;* (content:post-text contents.post) +== +++ comments +=/ =pid:tp [author.post id.post] +=/ board-path (trip (spat (board-path:lib t))) +=/ l (lent replies.t) +=/ length ?: .=(0 l) "No" "{<l>}" +=/ js "$store.replying_to.show('main')" +^- manx +;div#comments + ;a/"{board-path}":"Switch to Board View" + ;h3:"{length} comments" + ;div#big-reply + ;span.fl + =kaji "scry" + =path "/blog/f/reply/{(enc:kaji [pid pid])}" + =swap "add" + =cont "#big-reply" + ; Leave a reply + == + == + ;+ comment-list +== +++ comment-list ^- manx +=/ cs (tap:form:tp children) +;div#comments-wrapper + ;ul#comments-top.comment-list + =id "children{(scow %ud id.post)}" + ;* (turn (flop cs) comment) + == +== + +++ comment +=| nested=@ud +|= [=pid:tp [p=post:tp c=full-graph:tp]] +^- manx +=/ author (get-name:lib -.pid) +=/ full-time (datetime-to-tape:string:sr id.pid "/") +=/ time ?. is-mobile full-time + (post-date-ago:lib id.pid now.bowl %tam) + +=/ id=tape (enc:kaji pid) +=/ js=tape "$store.replying_to.show('{id}')" +=/ post-meta (enc:kaji [[author.post id.post] pid]) +=/ permalink "" +;li + =id "comment-{id}" + ;img.collapse-button + =src "https://s3.spandrell.ch/assets/board/ui/collapse.svg" + =kaji "toggle" + =targ ".comment-proper/.uncollapse-button/.collapse-button" + ; + == + ;img.uncollapse-button + =src "https://s3.spandrell.ch/assets/board/ui/uncollapse.svg" + =kaji "toggle" + =targ ".comment-proper/.uncollapse-button/.collapse-button" + =hidden "" + ; + == + ;div.comment + ;div.comment-meta + ;div.row + ;a.comment-author/"/u/{author}":"{author}" + ; | + ;a.comment-time + =title "{time}" + =href permalink + ; {time} + == + ;* ?. (is-admin:lib src.bowl) ~ + ;= ;span:"|" + ;a + =kaji "poke" + =action "del-comment" + =name "pid" + =payload id + ; Nuke + == + == + == + == + ;div.comment-proper + ;div.comment-content + ;* (content:post-text contents.p) + == + ;span.fl.reply-prompt + =id id + =kaji "scry" + =path "/blog/f/reply/{post-meta}" + =swap "swap" + =targ ".reply-box" + ; reply + == + ;div.reply-box; + ;+ (grandchildren [p c] +(nested)) + == + == +== +++ shit +=| nested=@ud +|= a=@ud %lol + +++ grandchildren +|= [p=full-node:tp nested=@ud] ^- manx +=/ cs (tap:form:tp children.p) +=/ c (houyi:plib children.p) +=/ count (scow:parsing:sr %ud c) + +?: ?& (gth (lent cs) 0) (gth nested 5) is-mobile == + =/ pids [[author.post id.post] [author.p.p id.p.p]] + =/ path "/blog/f/subthread/{(enc:kaji pids)}" + ;a + =kaji "scry" + =path path + =swap "swap" + =targ "#comments-wrapper" + ; Show {count} more comments + == +=/ id=tape (enc:kaji [author.p.p id.p.p]) +=/ newcomms comment :: need to tisfas it to be able to manipulate the optional variable + :: I don't get it +;ul.comment-list.nested + =id "children-{id}" + ;* (turn (flop cs) newcomms(nested nested)) +== +++ comment-subthread +|= fn=full-node:tp +=/ cs (tap:form:tp children.fn) +=/ ocs (tap:form:tp children) +=/ c (houyi:plib children.fn) +=/ oc (houyi:plib children) +=/ count (scow:parsing:sr %ud c) +=/ total (scow:parsing:sr %ud oc) +=/ =pid:tp [author.post id.post] +;div#subthread + ;div + ;p:"Showing {count} comments of {total}" + ;a + =kaji "scry" + =path "/blog/f/comments/{(enc:kaji pid)}" + =swap "swap" + =targ "#comments-wrapper" + ; Go back to top + == + == + ;div#comments-top.comment-list + =id "children{(scow %ud id.post)}" + ;* (turn (flop cs) comment) + == +== + + +++ html ^- manx + ;div.blog + ;+ (post-header t src.bowl) + ;+ post-content + ;+ comments + ;script:"{script}" + == +-- diff --git a/desk/web/blog/router.hoon b/desk/web/blog/router.hoon new file mode 100644 index 0000000..32a96c9 --- /dev/null +++ b/desk/web/blog/router.hoon @@ -0,0 +1,198 @@ +/- boke, cnt=contact, tp=trill-post +/+ kaji, fetch-lib=fetch, plib=trill-utils, const=constants, sr=sortug, lib=boke, ui=trill-ui +/= index /web/index +/= post-list /web/blog/post-list +/= post-show /web/blog/post +/= title /web/blog/title +/= user /web/components/user + +|_ [rl=req-line:kaji s=state:boke =bowl:gall] ++* fetch ~(. fetch-lib [s bowl]) + +++ eyre-bail (error-response:kaji 404) +++ manx-bail (error-page:kaji 404) +:: +++ $ ^- eyre-res:kaji + =/ p pat.rl ::?. mob.rl pat.rl [%m pat.rl] + ?+ p ~& >>> path-not-found=`path`p eyre-bail + ~ (serve-page par.rl) + [%snip pid=@ ~] (post-snippet pid.p) + :: + [%f rest=*] (fragment rest.p) + [%b rest=*] (threads rest.p) + == +++ serve-index + :- %page +:: =/ paginated (get-tag-page:plib feed.s tags.s ~['blog'] [~ ~ page-size]) + =/ paginated (thread-page-by-tags:fetch ~['blog'] [~ ~ blog-page-size:const] ~) + :: =/ paginated ~> %bout (get-tag-page:plib feed.s tags.s ~['blog'] [~ ~ page-size]) + =/ main=manx (html:post-list paginated ~) + (index ~[title main] bowl) + +++ serve-page +|= params=(map @t @t) + :- %page ^- manx + =/ tags + =/ ptags (~(get by params) 't') + ?~ ptags ~['blog'] + =/ parsed (parse-tags:ui u.ptags) + ?~ parsed ~['blog'] :: TODO some error here + ~(tap in u.parsed) + =/ older + =/ pcursor (~(get by params) 'cursor') ?~ pcursor ~ + (slaw:parsing:sr %uw u.pcursor) + =/ req [~ older blog-page-size:const] + =/ paginated (thread-page-by-tags:fetch tags req ~) + =/ tag-title + =/ ptags (~(get by params) 't') + ?~ ptags ~ + ;+ ;h3.tag-title:"Posts tagged as {(trip u.ptags)}" + =/ main=manx (html:post-list paginated tag-title) + (index ~[title main] bowl) + + + + + +++ show + :- %page + =/ dat (op-by-path:fetch pat.rl) + ?~ dat manx-bail + =/ main=manx + ~(html post-show [u.dat bowl mob.rl]) + (index ~[main] bowl) + +++ threads +|= p=(pole knot) + :- %page + =/ dat (op-by-path:fetch p) + ?~ dat manx-bail + =/ main=manx + ~(html post-show [u.dat bowl mob.rl]) + (index ~[main] bowl) +:: don't think we're using this now +++ post-snippet + |= [ss=@] + :- %html + =/ pid (dec:kaji ss pid:tp) + ?~ pid manx-bail + :: =/ post (get:gorm:tp feed.s u.pid) + =/ thread (get:torm:tp threads.s u.pid) + ?~ thread manx-bail + :: =/ fn (node-to-full:plib u.post feed.s) + (post-snippet:post-list u.thread) + + + + + + :: ?+ pat bail + :: [type=@t name=@t cursor=@t ~] + :: :: TODO %feed type at some point? + :: =/ older (slaw:parsing:sr %uw cursor.pat) ?~ older inline-error + :: ?. ?=(?(%b %t) type.pat) inline-error + :: ?: ?=(%b type.pat) + :: =/ page (board-to-page:lib name.pat tags.s feed.s [~ older board-page-size:cns]) + :: ?~ page inline-error + :: (serve-post-page name.pat u.page .n) + :: :: single tag + :: =/ page (tag-to-page:lib name.pat tags.s feed.s [~ older board-page-size:cns]) ?~ page inline-error + :: (serve-post-page name.pat u.page .y) + :: == + :: ++ serve-post-page + :: |= [name=@t =page:tp is-tag=?] + :: ~& >> serving-page=+.page + :: =/ previews ?: ?=(%blog name) + :: (turn p.page post-snippet:post-list) + :: :: + :: =/ contacts (get-contacts:cnt bowl) + :: %+ turn p.page |= fn=full-node:tp (thread-preview fn contacts bowl) + :: ;div + :: ;* %+ snoc previews + :: (cursor:cm older.page "Older" name is-tag) + :: == + ++ fragment + |= p=(pole knot) + :- %html + ?+ p manx-bail + ~ blog-page + [%comments uid=@t ~] (blog-comments uid.p) + [%subthread uid=@t ~] (subthread uid.p) + [%reply uid=@t ~] (reply-box uid.p) + == + ++ blog-page + =/ params=(map @t @t) par.rl + =/ tags + =/ ptags (~(get by params) 't') + ?~ ptags ~['blog'] + =/ parsed (parse-tags:ui u.ptags) + ?~ parsed ~['blog'] ~(tap in u.parsed) + =/ older + =/ pcursor (~(get by params) 'cursor') ?~ pcursor ~ + (slaw:parsing:sr %uw u.pcursor) + =/ req=page-req:tp [~ older blog-page-size:const] + :: =/ paginated (get-tag-page-2:plib feed.s tags.s tags req) + =/ paginated (thread-page-by-tags:fetch tags req ~) + (html:post-list paginated ~) +++ blog-comments +|= hash=@t + =/ op (op-by-hash:fetch hash) + ?~ op manx-bail + ~(comment-list post-show [u.op bowl mob.rl]) + +++ subthread +|= hash=@t + =/ both (dec:kaji hash ,[op=pid:tp sub=pid:tp]) + ?~ both manx-bail + =/ op (op-by-pid:fetch op.u.both) + ?~ op manx-bail + =/ sub-op (fn-by-pid:fetch sub.u.both) + ?~ sub-op manx-bail + =/ core ~(. post-show [u.op bowl mob.rl]) + + (comment-subthread:core u.sub-op) + +++ reply-box +|= hash=@t +?: .=(%pawn (clan:^title src.bowl)) +;div#reply-box + ;p:"You must login with an Urbit ID to comment" +== +=/ usr (user src.bowl *whoms:cnt 30) :: TODO + ;div + ;div#reply-box + ;span + =id "cancel-reply" + =class "fl" + =kaji "destroy" + =targ "#reply-box" + ; Cancel reply + == + ;form#reply-form + =kaji "poke" + =action "add-reply" + ;textarea#comment + =name "text" + =cols "45" + =rows "8" + =maxlength "6552" + =autocomplete "off" + ; + == + ;div.author-data + ;div.as.flex + ;span:"Posting as " + ;div.flex.ml1 + ;+ avatar.usr + ;+ name.usr + == + == + ;input#submit-comment(type "submit", value "Poast"); + == + ;input(type "hidden", name "parents", value (trip hash)); + ;input(type "hidden", name "is-blog", value ".y"); + ;span#error-div; + == + == + == +-- diff --git a/desk/web/blog/title.hoon b/desk/web/blog/title.hoon new file mode 100644 index 0000000..7675165 --- /dev/null +++ b/desk/web/blog/title.hoon @@ -0,0 +1,11 @@ +=< html +|% +++ html ^- manx + ;header + ;h1 + ;a/"/":"Bloody Shovel 4" + == + ;h4:"Nemo nos Salvabit" + :: ;h4:"不當而當亂也" + == +--
\ No newline at end of file |