diff --git a/plugin.json b/plugin.json index 12e48ad..20edd21 100644 --- a/plugin.json +++ b/plugin.json @@ -1,26 +1,4 @@ { - "id": "nodebb-theme-quickstart", - "hooks": [ - { "hook": "static:app.load", "method": "init" }, - { "hook": "filter:admin.header.build", "method": "addAdminNavigation" }, - { "hook": "filter:widgets.getAreas", "method": "defineWidgetAreas" }, - { "hook": "filter:config.get", "method": "getThemeConfig" }, - { "hook": "filter:settings.get", "method": "getAdminSettings"}, - { "hook": "filter:user.saveSettings", "method": "saveUserSettings" }, - { "hook": "filter:user.profileMenu", "method": "addProfileItem" }, - { "hook": "filter:middleware.renderHeader", "method": "filterMiddlewareRenderHeader" } - ], - "scripts": [ - "public/client.js", - "../nodebb-theme-harmony/public/harmony.js" - ], - "templates": "templates", - "modules": { - "../admin/plugins/theme-quickstart.js": "../nodebb-theme-harmony/public/admin.js", - "../client/account/theme.js": "../nodebb-theme-harmony/public/settings.js" - }, - "staticDirs": { - "inter": "node_modules/@fontsource/inter/files", - "poppins": "node_modules/@fontsource/poppins/files" - } -} + "id": "nodebb-theme-rockfic", + "languages": "languages" +} \ No newline at end of file diff --git a/templates/account/info.tpl b/templates/account/info.tpl new file mode 100644 index 0000000..3df8bbf --- /dev/null +++ b/templates/account/info.tpl @@ -0,0 +1,292 @@ +<!-- IMPORT partials/account/header.tpl --> + +<!-- IF sessions.length --> +<div class="row mb-3"> + <div class="col-12 col-md-12"> + <h4>[[global:sessions]]</h4> + <ul class="list-group" component="user/sessions"> + {{{each sessions}}} + <li class="list-group-item" data-uuid="{../uuid}"> + <div class="float-end"> + <!-- IF isSelfOrAdminOrGlobalModerator --> + <!-- IF !../current --> + <button class="btn btn-sm btn-outline-secondary" type="button" data-action="revokeSession">Revoke Session</button> + <!-- ENDIF !../current --> + <!-- ENDIF isSelfOrAdminOrGlobalModerator --> + {function.userAgentIcons} + <i class="fa fa-circle text-<!-- IF ../current -->success<!-- ELSE -->muted<!-- ENDIF ../current -->"></i> + </div> + {../browser} {../version} on {../platform}<br /> + <small class="timeago text-muted" title="{../datetimeISO}"></small> + <ul> + <li><strong>[[global:ip-address]]</strong>: {../ip}</li> + </ul> + </li> + {{{end}}} + </ul> + </div> +</div> +<!-- ENDIF sessions.length --> + +<div class="row"> + <div class="col-sm-6"> + <div class="card mb-3"> + <h5 class="card-header"> + [[global:recentips]] + </h5> + <div class="card-body"> + <ul> + {{{each ips}}} + <li>{@value}</li> + {{{end}}} + </ul> + </div> + </div> + + <div class="card mb-3"> + <h5 class="card-header"> + [[user:info.username-history]] + </h5> + <div class="card-body"> + <ul class="list-unstyled mb-0"> + {{{ each usernames }}} + <li class="d-flex justify-content-between mb-1"> + <span class="text-sm">{./value}</span> + + <div> + {{{ if ./byUid }}} + <a class="lh-1" href="{{{ if ./byUser.remoteId }}}{config.relative_path}/user/{./byUser.remoteId}{{{ else }}}#{{{ end }}}"> + {buildAvatar(./byUser, "18px", true)}</a> + {{{ end }}} + <span class="timeago text-sm lh-1" title="{./timestampISO}"></span> + </div> + </li> + {{{ end }}} + </ul> + </div> + </div> + + <div class="card mb-3"> + <h5 class="card-header"> + [[user:info.email-history]] + </h5> + <div class="card-body"> + <ul class="list-unstyled mb-0"> + {{{ each emails }}} + <li class="d-flex justify-content-between mb-1"> + <span class="text-sm">{./value}</span> + <div> + {{{ if ./byUid }}} + <a class="lh-1" href="{{{ if ./byUser.remoteId }}}{config.relative_path}/user/{./byUser.remoteId}{{{ else }}}#{{{ end }}}"> + {buildAvatar(./byUser, "18px", true)}</a> + {{{ end }}} + <span class="timeago text-sm lh-1" title="{./timestampISO}"></span> + </div> + </li> + {{{ end }}} + </ul> + </div> + </div> + <!-- IF isAdminOrGlobalModerator --> + <div class="card"> + <h5 class="card-header"> + [[user:info.moderation-note]] + </h5> + <div class="card-body"> + <textarea component="account/moderation-note" class="form-control"></textarea> + <br/> + <button class="btn btn-sm float-end btn-success" component="account/save-moderation-note">[[user:info.moderation-note.add]]</button> + <br/> + <div component="account/moderation-note/list"> + {{{ each moderationNotes }}} + <hr/> + + <div data-id="{./id}"> + <div class="mb-1"> + <a href="{{{ if ./user.remoteId }}}{config.relative_path}/user/{./user.remoteId}{{{ else }}}#{{{ end }}}">{buildAvatar(./user, "24px", true)}</a> + + <a href="{{{ if ./user.remoteId }}}{config.relative_path}/user/{./user.remoteId}{{{ else }}}#{{{ end }}}" class="fw-bold" itemprop="author" data-username="{./user.username}" data-uid="{./user.uid}">{./user.username}</a> + + <span class="timeago" title="{./timestampISO}"></span> + </div> + + <div component="account/moderation-note/content-area" class="d-flex flex-column"> + <div class="content"> + {./note} + </div> + <button component="account/moderation-note/edit" class="btn btn-sm btn-link align-self-end">[[topic:edit]]</button> + </div> + + <div component="account/moderation-note/edit-area" class="d-flex flex-column gap-2"> + <textarea class="form-control w-100 overflow-hidden">{./rawNote}</textarea> + <div class="align-self-end"> + <button component="account/moderation-note/cancel-edit" class="btn btn-sm btn-link text-danger align-self-end">[[global:cancel]]</button> + <button component="account/moderation-note/save-edit" class="btn btn-sm btn-primary align-self-end">[[global:save]]</button> + </div> + </div> + </div> + {{{ end }}} + </div> + <!-- IMPORT partials/paginator.tpl --> + </div> + </div> + <!-- ENDIF isAdminOrGlobalModerator --> + </div> + <div class="col-sm-6"> + <div class="card mb-3"> + <h5 class="card-header"> + [[user:info.latest-flags]] + </h5> + <div class="card-body"> + <!-- IF history.flags.length --> + <ul class="recent-flags list-unstyled"> + {{{ each history.flags }}} + <li class="mb-4 border-bottom"> + <div class="mb-1 d-flex align-items-center justify-content-between"> + <div> + {{{ if (./type == "user")}}} + <span class="badge text-bg-info">[[user:info.profile]]</span> + {{{ else }}} + <span class="badge text-bg-info">[[user:info.post]]</span> + {{{ end }}} + <span class="timestamp timeago" title="{./timestampISO}"></span> + </div> + + <a href="{config.relative_path}/flags/{./flagId}" class="badge badge border border-gray-300 text-body">[[user:info.view-flag]]</a> + </div> + + {{{ if (./type == "post") }}} + <p class="mb-1"> + {{{ if history.flags.targetPurged }}} + <div>[[flags:target-purged]]</div> + {{{ else }}} + <a class="title" href="{config.relative_path}/post/{./pid}">{./title}</a> + {{{ end }}} + </p> + {{{ end }}} + + <div class="d-flex gap-2 align-items-center mb-3"> + <span class="text-sm">[[user:info.reported-by]]</span> + <div class="d-flex text-nowrap"> + {{{ each ./reports }}} + <a style="width: 18px; z-index: 3;" class="text-decoration-none" href="{config.relative_path}/user/{./reporter.remoteId}">{buildAvatar(./reporter, "24px", true)}</a> + {{{ end }}} + </div> + </div> + </li> + {{{ end }}} + </ul> + <!-- ELSE --> + <div class="alert alert-success">[[user:info.no-flags]]</div> + <!-- ENDIF history.flags.length --> + </div> + </div> + + <div class="card mb-3"> + <h5 class="card-header"> + [[user:info.ban-history]] + + <!-- IF !banned --> + <!-- IF !isSelf --> + <button class="btn btn-sm float-end btn-danger" component="account/ban">[[user:ban-account]]</button> + <!-- ENDIF !isSelf --> + <!-- ELSE --> + <!-- IF !isSelf --> + <button class="btn btn-sm float-end btn-success" component="account/unban">[[user:unban-account]]</button> + <!-- ENDIF !isSelf --> + <!-- ENDIF !banned --> + </h5> + <div class="card-body"> + <!-- IF history.bans.length --> + <ul class="ban-history list-unstyled"> + {{{ each history.bans }}} + <li class="mb-4 border-bottom"> + <div class="mb-1 d-flex align-items-center justify-content-between"> + <div> + <a href="{config.relative_path}/user/{./user.remoteId}">{buildAvatar(./user, "24px", true)}</a> + <strong> + <a href="{{{ if ./user.remoteId }}}{config.relative_path}/user/{./user.remoteId}{{{ else }}}#{{{ end }}}" itemprop="author" data-username="{./user.username}" data-uid="{./user.uid}">{./user.username}</a> + </strong> + <span class="timestamp timeago" title="{./timestampISO}"></span> + </div> + {{{ if (./type != "unban") }}} + <span class="badge text-bg-danger">[[user:banned]]</span> + {{{ else }}} + <span class="badge text-bg-success">[[user:unbanned]]</span> + {{{ end }}} + </div> + <p class="mb-1"> + <span class="reason">[[user:info.banned-reason-label]]: <strong>{./reason}</strong></span> + </p> + <p class=""> + {{{ if ./until }}} + <span class="expiry">[[user:info.banned-until, {isoTimeToLocaleString(./untilISO, config.userLang)}]]</span> + {{{ else }}} + {{{ if (./type != "unban") }}} + <span class="expiry">[[user:info.banned-permanently]]</span> + {{{ end }}} + {{{ end }}} + </p> + </li> + {{{ end }}} + </ul> + <!-- ELSE --> + <div class="alert alert-success">[[user:info.no-ban-history]]</div> + <!-- ENDIF history.bans.length --> + </div> + </div> + + <div class="card mb-3"> + <h5 class="card-header"> + [[user:info.mute-history]] + + {{{ if !muted }}} + {{{ if !isSelf }}} + <button class="btn btn-sm float-end btn-danger" component="account/mute">[[user:mute-account]]</button> + {{{ end }}} + {{{ else }}} + {{{ if !isSelf }}} + <button class="btn btn-sm float-end btn-success" component="account/unmute">[[user:unmute-account]]</button> + {{{ end }}} + {{{ end }}} + </h5> + <div class="card-body"> + {{{ if history.mutes.length }}} + <ul class="ban-history list-unstyled"> + {{{ each history.mutes }}} + <li class="mb-4 border-bottom"> + <div class="mb-1 d-flex align-items-center justify-content-between"> + <div> + <a href="{config.relative_path}/user/{./user.remoteId}">{buildAvatar(./user, "24px", true)}</a> + <strong> + <a href="{{{ if ./user.remoteId }}}{config.relative_path}/user/{./user.remoteId}{{{ else }}}#{{{ end }}}" itemprop="author" data-username="{./user.username}" data-uid="{./user.uid}">{./user.username}</a> + </strong> + <span class="timestamp timeago" title="{./timestampISO}"></span> + </div> + {{{ if (./type != "unmute") }}} + <span class="badge text-bg-danger">[[user:muted]]</span> + {{{ else }}} + <span class="badge text-bg-success">[[user:unmuted]]</span> + {{{ end }}} + </div> + <p class="mb-1"> + <span class="reason">[[user:info.banned-reason-label]]: <strong>{./reason}</strong></span> + </p> + <p class=""> + {{{ if ./until }}} + <span class="expiry">[[user:info.muted-until, {isoTimeToLocaleString(./untilISO, config.userLang)}]]</span> + {{{ end }}} + </p> + </li> + {{{ end }}} + </ul> + {{{ else }}} + <div class="alert alert-success">[[user:info.no-mute-history]]</div> + {{{ end }}} + </div> + </div> + + </div> +</div> + +<!-- IMPORT partials/account/footer.tpl --> \ No newline at end of file diff --git a/templates/partials/categories/lastpost.tpl b/templates/partials/categories/lastpost.tpl index c7965cb..134a54a 100644 --- a/templates/partials/categories/lastpost.tpl +++ b/templates/partials/categories/lastpost.tpl @@ -3,7 +3,7 @@ {{{ if @first }}} <div component="category/posts" class="ps-2 text-xs d-flex flex-column h-100 gap-1"> <div class="text-nowrap text-truncate"> - <a class="text-decoration-none avatar-tooltip" title="{./user.displayname}" href="{config.relative_path}/user/{./user.userslug}">{buildAvatar(posts.user, "18px", true)}</a> + <a class="text-decoration-none avatar-tooltip" title="{./user.username}" href="{config.relative_path}/user/{./user.remoteId}">{buildAvatar(posts.user, "18px", true)}</a> <a class="permalink text-muted timeago text-xs" href="{config.relative_path}/topic/{./topic.slug}{{{ if ./index }}}/{./index}{{{ end }}}" title="{./timestampISO}" aria-label="[[global:lastpost]]"></a> </div> <div class="post-content text-xs text-break line-clamp-sm-2 lh-sm position-relative flex-fill"> diff --git a/templates/partials/chats-menu.tpl b/templates/partials/chats-menu.tpl new file mode 100644 index 0000000..fd12ebb --- /dev/null +++ b/templates/partials/chats-menu.tpl @@ -0,0 +1,41 @@ +{{{ if config.loggedIn }}} +<ul class="nav nav-pills"> + <li class="nav-item"> + <a class="nav-link text-decoration-none" href="#" data-bs-target="#notifications" data-bs-toggle="tab"><span class="counter unread-count" component="notifications/icon" data-content="{unreadCount.notification}"></span> <i class="fa fa-fw fa-bell"></i></a> + </li> + {{{ if !config.disableChat }}} + <li class="nav-item"> + <a class="nav-link text-decoration-none" href="#" data-bs-target="#chats" data-bs-toggle="tab"><i class="counter unread-count" component="chat/icon" data-content="{unreadCount.chat}"></i> <i class="fa fa-fw fa-comment"></i></a> + </li> + {{{ end }}} + <li class="nav-item"> + <a class="nav-link active text-decoration-none" href="#" data-bs-target="#profile" data-bs-toggle="tab"> + {buildAvatar(user, "24px", true, "user-icon")} + </a> + </li> +</ul> + +<div class="tab-content"> + <div class="tab-pane fade show active" id="profile"> + <section class="menu-section" data-section="profile"> + <ul class="menu-section-list dropdown-menu show text-bg-dark w-100 border-0" component="header/usercontrol"></ul> + </section> + </div> + <div class="tab-pane fade" id="notifications"> + <section class="menu-section text-bg-dark px-1" data-section="notifications"> + <ul class="menu-section-list notification-list-mobile list-unstyled" component="notifications/list"></ul> + <div class="menu-section-list text-center p-3"><a href="{relative_path}/notifications">[[notifications:see-all]]</a></div> + </section> + </div> + {{{ if !config.disableChat }}} + <div class="tab-pane fade" id="chats"> + <section class="menu-section text-bg-dark px-1" data-section="chats"> + <ul class="menu-section-list chat-list list-unstyled" component="chat/list"> + + </ul> + <div class="menu-section-list text-center p-3"><a class="navigation-link" href="{relative_path}/user/{user.remoteId}/chats">[[modules:chat.see-all]]</a></div> + </section> + </div> + {{{ end }}} +</div> +{{{ end }}} \ No newline at end of file diff --git a/templates/partials/groups/invited.tpl b/templates/partials/groups/invited.tpl new file mode 100644 index 0000000..aa24837 --- /dev/null +++ b/templates/partials/groups/invited.tpl @@ -0,0 +1,33 @@ +<label class="text-xs text-muted">[[groups:invited.search]]</label> +<div class="input-group mb-2"> + <input class="form-control" type="text" component="groups/members/invite"/> + <span class="input-group-text search-button"><i class="fa fa-search"></i></span> +</div> + +<div class="mb-2"> + <label class="text-xs text-muted">[[groups:bulk-invite-instructions]]</label> + <textarea class="form-control" component="groups/members/bulk-invite"></textarea> +</div> + +<div class="mb-2 clearfix"> + <button type="button" class="btn btn-primary btn-sm float-end" component="groups/members/bulk-invite-button">[[groups:bulk-invite]]</button> +</div> + +<div style="max-height: 500px; overflow: auto;"> + <div component="groups/invited/alert" class="alert alert-info {{{ if group.invited.length }}}hidden{{{ end }}}">[[groups:invited.none]]</div> + <table component="groups/invited" class="table table-hover"> + <tbody> + {{{ each group.invited }}} + <tr data-uid="{group.invited.uid}" class="align-middle"> + <td class="member-name p-2 d-flex align-items-center justify-content-between"> + <div class="d-flex align-items-center gap-2"> + <a class="text-decoration-none" href="{config.relative_path}/user/{group.invited.remoteId}">{buildAvatar(group.invited, "24px", true)}</a> + <a href="{config.relative_path}/user/{group.invited.remoteId}">{group.invited.username}</a> + </div> + <button class="btn btn-outline-secondary btn-sm text-nowrap" data-action="rescindInvite">[[groups:invited.uninvite]]</button> + </td> + </tr> + {{{ end }}} + </tbody> + </table> +</div> \ No newline at end of file diff --git a/templates/partials/groups/list.tpl b/templates/partials/groups/list.tpl new file mode 100644 index 0000000..cfd60b5 --- /dev/null +++ b/templates/partials/groups/list.tpl @@ -0,0 +1,21 @@ +{{{each groups}}} +<div class="col-lg-4 col-md-6 col-sm-12 mb-3" component="groups/summary" data-slug="{groups.slug}"> + <div class="card h-100"> + <a href="{config.relative_path}/groups/{groups.slug}" class="card-header list-cover" style="{{{ if groups.cover:thumb:url }}}background-image: url({./cover:thumb:url});background-size: cover; min-height: 125px; background-position: {./cover:position}{{{ end }}}"> + <h5 class="card-title d-inline-block mw-100 px-2 py-1 text-truncate text-capitalize fw-bold rounded-1" style="color: white;background-color: rgba(0,0,0,0.5);">{groups.displayName} <small>{formattedNumber(groups.memberCount)}</small></h5> + </a> + <div class="card-body"> + <p class="text-muted"> + {./description} + </p> + <ul class="members list-unstyled d-flex align-items-center gap-2 flex-wrap"> + {{{each groups.members}}} + <li> + <a href="{config.relative_path}/user/{groups.members.remoteId}">{buildAvatar(groups.members, "24px", true)}</a> + </li> + {{{end}}} + </ul> + </div> + </div> +</div> +{{{end}}} \ No newline at end of file diff --git a/templates/partials/groups/memberlist.tpl b/templates/partials/groups/memberlist.tpl new file mode 100644 index 0000000..0b67b31 --- /dev/null +++ b/templates/partials/groups/memberlist.tpl @@ -0,0 +1,43 @@ +<div class="d-flex mb-3"> + <!-- IF group.isOwner --> + <div class="flex-shrink-0"> + <button component="groups/members/add" type="button" class="btn btn-primary me-3" title="[[groups:details.add-member]]"><i class="fa fa-user-plus"></i></button> + </div> + <!-- ENDIF group.isOwner --> + <div class="flex-grow-1"> + <div class="input-group"> + <input class="form-control" type="text" component="groups/members/search" placeholder="[[global:search]]"/> + <span class="input-group-text search-button"><i class="fa fa-search"></i></span> + </div> + </div> +</div> + +<div component="groups/members" data-nextstart="{group.membersNextStart}" style="max-height: 500px; overflow: auto;"> + <table class="table table-striped table-hover"> + <tbody> + {{{each group.members}}} + <tr data-uid="{group.members.uid}" data-isowner="{{{ if group.members.isOwner }}}1{{{ else }}}0{{{ end }}}"> + <td class="p-2"> + <a href="{config.relative_path}/user/{group.members.remoteId}">{buildAvatar(group.members, "24px", true)}</a> + </td> + <td class="member-name w-100 p-2"> + <a class="align-text-top" href="{config.relative_path}/user/{group.members.remoteId}">{group.members.username}</a> + <i component="groups/owner/icon" title="[[groups:owner]]" class="user-owner-icon fa fa-star align-text-top text-warning <!-- IF !group.members.isOwner -->invisible<!-- ENDIF !group.members.isOwner -->"></i> + + <!-- IF group.isOwner --> + <div class="owner-controls btn-group float-end"> + <a class="btn btn-sm" href="#" data-ajaxify="false" data-action="toggleOwnership" title="[[groups:details.grant]]"> + <i class="fa fa-star"></i> + </a> + + <a class="btn btn-sm" href="#" data-ajaxify="false" data-action="kick" title="[[groups:details.kick]]"> + <i class="fa fa-ban"></i> + </a> + </div> + <!-- ENDIF group.isOwner --> + </td> + </tr> + {{{end}}} + </tbody> + </table> +</div> \ No newline at end of file diff --git a/templates/partials/groups/pending.tpl b/templates/partials/groups/pending.tpl new file mode 100644 index 0000000..f2e75d8 --- /dev/null +++ b/templates/partials/groups/pending.tpl @@ -0,0 +1,28 @@ +{{{ if group.pending.length }}} +<div class="d-flex justify-content-end gap-2 mb-3"> + <button class="btn btn-danger btn-sm" data-action="rejectAll">[[groups:pending.reject-all]]</button> + <button class="btn btn-success btn-sm" data-action="acceptAll">[[groups:pending.accept-all]]</button> +</div> +{{{ end }}} + +<div style="max-height: 500px;overflow: auto;"> + <div component="groups/pending/alert" class="alert alert-info {{{ if group.pending.length }}}hidden{{{ end }}}">[[groups:pending.none]]</div> + <table component="groups/pending" class="table table-hover"> + <tbody> + {{{ each group.pending }}} + <tr data-uid="{group.pending.uid}" class="align-middle"> + <td class="member-name p-2 d-flex align-items-center justify-content-between"> + <div class="d-flex gap-2"> + <a class="text-decoration-none" href="{config.relative_path}/user/{group.pending.remoteId}">{buildAvatar(group.pending, "24px", true)}</a> + <a href="{config.relative_path}/user/{group.pending.remoteId}">{group.pending.username}</a> + </div> + <div class="d-flex gap-2"> + <button class="btn btn-danger btn-sm" data-action="reject">[[groups:pending.reject]]</a></li> + <button class="btn btn-success btn-sm" data-action="accept">[[groups:pending.accept]]</a></li> + </div> + </td> + </tr> + {{{ end }}} + </tbody> + </table> +</div> diff --git a/templates/partials/header/chats.tpl b/templates/partials/header/chats.tpl new file mode 100644 index 0000000..0b54f1b --- /dev/null +++ b/templates/partials/header/chats.tpl @@ -0,0 +1,35 @@ +<a class="nav-link" data-bs-toggle="dropdown" href="{relative_path}/user/{user.remoteId}/chats" data-ajaxify="false" id="chat_dropdown" component="chat/dropdown" role="button" aria-haspopup="true" aria-expanded="false"> + <i component="chat/icon" class="fa {{{ if unreadCount.chat}}}fa-comment{{{ else }}}fa-comment-o{{{ end }}} fa-fw unread-count" data-content="{unreadCount.chat}"></i> <span class="d-inline d-sm-none">[[global:header.chats]]</span> +</a> +<ul class="dropdown-menu dropdown-menu-end p-1" aria-labelledby="chat_dropdown" role="menu"> + <li> + <ul component="chat/list" class="list-unstyled chat-list chats-list ghost-scrollbar pe-1"> + <div class="rounded-1"> + <div class="d-flex gap-1 justify-content-between"> + <div class="dropdown-item p-2 d-flex gap-2 placeholder-wave"> + <div class="main-avatar"> + <div class="placeholder" style="width: 32px; height: 32px;"></div> + </div> + <div class="d-flex flex-grow-1 flex-column w-100"> + <div class="text-xs"><div class="placeholder col-3"></div></div> + <div class="text-sm"><div class="placeholder col-11"></div></div> + <div class="text-xs"><div class="placeholder col-4"></div></div> + </div> + </div> + <div> + <button class="mark-read btn btn-ghost btn-sm d-flex align-items-center justify-content-center flex-grow-0 flex-shrink-0 p-1" style="width: 1.5rem; height: 1.5rem;"> + <i class="unread fa fa-2xs fa-circle text-primary"></i> + </button> + </div> + </div> + </div> + </ul> + </li> + <li class="dropdown-divider"></li> + <li> + <div class="d-flex justify-content-center gap-1 flex-wrap"> + <a class="btn btn-light btn-sm mark-all-read flex-fill text-nowrap" href="#" component="chats/mark-all-read"><i class="fa fa-check-double"></i> [[modules:chat.mark-all-read]]</a> + <a class="btn btn-primary btn-sm flex-fill text-nowrap" href="{relative_path}/user/{user.remoteId}/chats"><i class="fa fa-comments"></i> [[modules:chat.see-all]]</a> + </div> + </li> +</ul> \ No newline at end of file diff --git a/templates/partials/header/user-menu.tpl b/templates/partials/header/user-menu.tpl new file mode 100644 index 0000000..1b873c2 --- /dev/null +++ b/templates/partials/header/user-menu.tpl @@ -0,0 +1,102 @@ +<li id="user_label" class="nav-item dropdown px-3" title="[[global:header.profile]]"> + <a href="#" for="user-control-list-check" data-bs-toggle="dropdown" id="user_dropdown" role="button" component="header/avatar" aria-haspopup="true" aria-expanded="false"> + {buildAvatar(user, "32px", true)} + <span id="user-header-name" class="d-block d-sm-none">{user.username}</span> + </a> + <input type="checkbox" class="hidden" id="user-control-list-check" aria-hidden="true"> + <ul id="user-control-list" component="header/usercontrol" class="overscroll-behavior-contain user-dropdown dropdown-menu dropdown-menu-end shadow p-1 text-sm ff-base" role="menu"> + <li> + <a class="dropdown-item rounded-1 d-flex align-items-center gap-2" component="header/profilelink" href="{relative_path}/user/{user.remoteId}" role="menuitem" aria-label="[[user:profile]]"> + <span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status {user.status}"><span class="visually-hidden">[[global:{user.status}]]</span></span> + <span class="fw-semibold" component="header/username">{user.username}</span> + </a> + </li> + <li role="presentation" class="dropdown-divider"></li> + <li><h6 class="dropdown-header text-xs">[[global:status]]</h6></li> + <li> + <a href="#" class="dropdown-item rounded-1 user-status d-flex align-items-center gap-2 {{{ if user.online }}}selected{{{ end }}}" data-status="online" role="menuitem"> + <span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status online"></span> + <span class="flex-grow-1">[[global:online]]</span> + </a> + </li> + <li> + <a href="#" class="dropdown-item rounded-1 user-status d-flex align-items-center gap-2 {{{ if user.away }}}selected{{{ end }}}" data-status="away" role="menuitem"> + <span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status away"></span> + <span class="flex-grow-1">[[global:away]]</span> + </a> + </li> + <li> + <a href="#" class="dropdown-item rounded-1 user-status d-flex align-items-center gap-2 {{{ if user.dnd }}}selected{{{ end }}}" data-status="dnd" role="menuitem"> + <span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status dnd"></span> + <span class="flex-grow-1">[[global:dnd]]</span> + </a> + </li> + <li> + <a href="#" class="dropdown-item rounded-1 user-status d-flex align-items-center gap-2 {{{ if user.offline }}}selected{{{ end }}}" data-status="offline" role="menuitem"> + <span component="user/status" class="flex-shrink-0 border border-white border-2 rounded-circle status offline"></span> + <span class="flex-grow-1">[[global:invisible]]</span> + </a> + </li> + <li role="presentation" class="dropdown-divider"></li> + <li> + <a class="dropdown-item" href="{relative_path}/user/{user.remoteId}/bookmarks" role="menuitem"> + <i class="fa fa-fw fa-bookmark"></i> <span>[[user:bookmarks]]</span> + </a> + </li> + <li> + <a class="dropdown-item" component="header/profilelink/edit" href="{relative_path}/user/{user.remoteId}/edit" role="menuitem"> + <i class="fa fa-fw fa-edit"></i> <span>[[user:edit-profile]]</span> + </a> + </li> + <li> + <a class="dropdown-item" component="header/profilelink/settings" href="{relative_path}/user/{user.remoteId}/settings" role="menuitem"> + <i class="fa fa-fw fa-gear"></i> <span>[[user:settings]]</span> + </a> + </li> + {{{ if showModMenu }}} + <li role="presentation" class="dropdown-divider"></li> + <li><h6 class="dropdown-header">[[pages:moderator-tools]]</h6></li> + <li> + <a class="dropdown-item" href="{relative_path}/flags" role="menuitem"> + <i class="fa fa-fw fa-flag"></i> <span>[[pages:flagged-content]]</span> + </a> + </li> + <li> + <a class="dropdown-item" href="{relative_path}/post-queue" role="menuitem"> + <i class="fa fa-fw fa-list-alt"></i> <span>[[pages:post-queue]]</span> + </a> + </li> + {{{ if registrationQueueEnabled }}} + <li> + <a class="dropdown-item" href="{relative_path}/registration-queue" role="menuitem"> + <i class="fa fa-fw fa-list-alt"></i> <span>[[pages:registration-queue]]</span> + </a> + </li> + {{{ end }}} + <li> + <a class="dropdown-item" href="{relative_path}/ip-blacklist" role="menuitem"> + <i class="fa fa-fw fa-ban"></i> <span>[[pages:ip-blacklist]]</span> + </a> + </li> + {{{ else }}} + {{{ if postQueueEnabled }}} + <li> + <a class="dropdown-item" href="{relative_path}/post-queue" role="menuitem"> + <i class="fa fa-fw fa-list-alt"></i> <span>[[pages:post-queue]]</span> + </a> + </li> + {{{ end }}} + {{{ end }}} + + <li role="presentation" class="dropdown-divider"></li> + <li component="user/logout"> + <form method="post" action="{relative_path}/logout"> + <input type="hidden" name="_csrf" value="{config.csrf_token}"> + <input type="hidden" name="noscript" value="true"> + <button type="submit" class="dropdown-item" role="menuitem"> + <i class="fa fa-fw fa-sign-out"></i><span> [[global:logout]]</span> + </button> + </form> + </li> + </ul> +</li> \ No newline at end of file diff --git a/templates/partials/notifications_list.tpl b/templates/partials/notifications_list.tpl new file mode 100644 index 0000000..5c48d90 --- /dev/null +++ b/templates/partials/notifications_list.tpl @@ -0,0 +1,44 @@ +{{{ if !notifications.length }}} +<div class="no-notifs text-center p-4 d-flex flex-column"> + <div class="p-4"><i class="fa-solid fa-wind fs-2 text-muted"></i></div> + <div class="text-xs fw-semibold text-muted">[[notifications:no-notifs]]</div> +</div> +{{{ end }}} + +{{{ each notifications }}} +<div class="{./readClass}" data-nid="{./nid}" data-path="{./path}" {{{ if ./pid }}}data-pid="{./pid}"{{{ end }}}{{{ if ./tid }}}data-tid="{./tid}"{{{ end }}}> + <div class="d-flex gap-1 justify-content-between"> + <div class="btn btn-ghost btn-sm d-flex gap-2 flex-grow-1 align-items-start text-start"> + <a class="flex-grow-0 flex-shrink-0" href="{{{ if ./user.remoteId}}}{config.relative_path}/user/{./user.remoteId}{{{ else }}}#{{{ end }}}"> + {{{ if (./image && ./from) }}} + <img class="avatar avatar-rounded" style="--avatar-size: 32px;" src="{./image}" /> + {{{ else }}} + {{{ if ./icon }}} + <div class="avatar avatar-rounded" style="--avatar-size: 32px;"><i class="text-secondary fa {./icon}"></i></div> + {{{ else }}} + <div class="avatar avatar-rounded" style="--avatar-size: 32px; background-color: {./user.icon:bgColor};">{./user.icon:text}</div> + {{{ end }}} + {{{ end }}} + </a> + + <div class="d-flex flex-grow-1 flex-column align-items-start position-relative"> + <a href="{./path}" class="text-decoration-none d-inline-block text-reset text-break text-sm ff-sans stretched-link" component="notifications/item/link"> + {./bodyShort} + </a> + <div class="text-xs text-muted">{{{ if ./timeagoLong }}}{./timeagoLong}{{{ else }}}<span class="timeago" title="{./datetimeISO}"></span>{{{ end }}}</div> + </div> + </div> + <div> + {{{ if ./nid }}} + <button class="mark-read btn btn-ghost btn-sm flex-grow-0 flex-shrink-0 p-1" style="width: 1.5rem; height: 1.5rem;"> + <i class="unread fa fa-2xs fa-circle text-primary {{{ if ./read }}}hidden{{{ end }}}" aria-label="[[unread:mark-as-read]]"></i> + <i class="read fa fa-2xs fa-circle-o text-secondary {{{ if !./read }}}hidden{{{ end }}}" aria-label="[[unread:mark-as-unread]]"></i> + </button> + {{{ end }}} + </div> + </div> +</div> +{{{ if !@last }}} +<hr class="my-1" /> +{{{ end }}} +{{{ end }}} diff --git a/templates/partials/posts_list_item.tpl b/templates/partials/posts_list_item.tpl new file mode 100644 index 0000000..561cd51 --- /dev/null +++ b/templates/partials/posts_list_item.tpl @@ -0,0 +1,34 @@ +<li component="post" class="posts-list-item row {{{ if ./deleted }}} deleted{{{ else }}}{{{ if ./topic.deleted }}} deleted{{{ end }}}{{{ end }}}{{{ if ./topic.scheduled }}} scheduled{{{ end }}}" data-pid="{./pid}" data-uid="{./uid}"> + <div class="col-lg-11 col-sm-10 col-9 post-body pb-3"> + <a class="topic-title text-reset" href="{config.relative_path}/post/{encodeURIComponent(../pid)}"> + {{{ if !./isMainPost }}}RE: {{{ end }}}{./topic.title} + </a> + + <div component="post/content" class="content mb-3"> + {../content} + </div> + + <div class="mb-3"> + <a class="topic-category text-xs fw-bold text-uppercase text-secondary mb-3" href="{config.relative_path}/category/{../category.slug}">[[global:posted-in, {../category.name}]]</a> + + {{{ if ../isMainPost }}} + {{{ if ../topic.tags.length }}} + <span class="tag-list"> + {{{ each ../topic.tags }}} + <a href="{config.relative_path}/tags/{topic.tags.valueEncoded}"><span class="tag tag-item tag-class-{topic.tags.class}">{topic.tags.valueEscaped}</span></a> + {{{ end }}} + </span> + {{{ end }}} + {{{ end }}} + </div> + + <div class="post-info"> + <a href="{config.relative_path}/user/{./user.remoteId}">{buildAvatar(./user, "28px", true, "user-img not-responsive")}</a> + + <div class="post-author text-secondary text-uppercase"> + <a class="text-reset" href="{config.relative_path}/user/{./user.remoteId}">{./user.displayname}</a><br /> + <span class="timeago" title="{./timestampISO}"></span> + </div> + </div> + </div> +</li> \ No newline at end of file diff --git a/templates/partials/search-results.tpl b/templates/partials/search-results.tpl new file mode 100644 index 0000000..04efb3c --- /dev/null +++ b/templates/partials/search-results.tpl @@ -0,0 +1,54 @@ +<div id="results" class="search-results col-md-12" data-search-query="{search_query}"> + {{{ if matchCount }}} + <div class="alert alert-info">[[search:results-matching, {matchCount}, {search_query}, {time}]] </div> + {{{ else }}} + {{{ if search_query }}} + <div class="alert alert-warning">[[search:no-matches]]</div> + {{{ end }}} + {{{ end }}} + + {{{each posts}}} + <div class="topic-row card clearfix mb-3"> + <div class="card-body"> + <div class="mb-2"> + <a href="{config.relative_path}/user/{./user.remoteId}">{buildAvatar(./user, "24px", true)}</a> + <a class="topic-title fw-semibold fs-5" href="{config.relative_path}/post/{encodeURIComponent(posts.pid)}">{./topic.title}</a> + </div> + + {{{ if showAsPosts }}} + <div component="post/content" class="content"> + {./content} + </div> + {{{ end }}} + + <small class="post-info"> + <a href="{config.relative_path}/category/{./category.slug}"> + <div class="category-item d-inline-block"> + {buildCategoryIcon(./category, "24px", "rounded-circle")} + {./category.name} + </div> + </a> • + <span class="timeago" title="{./timestampISO}"></span> + </small> + </div> + </div> + {{{end}}} + + {{{ if users.length }}} + <!-- IMPORT partials/users_list.tpl --> + {{{ end }}} + + {{{ if tags.length }}} + <!-- IMPORT partials/tags_list.tpl --> + {{{ end }}} + + {{{ if categories.length }}} + <ul class="categories"> + {{{each categories}}} + <!-- IMPORT partials/categories/item.tpl --> + {{{end}}} + </ul> + {{{ end }}} + + <!-- IMPORT partials/paginator.tpl --> +</div> \ No newline at end of file diff --git a/templates/partials/topic/navigation-post.tpl b/templates/partials/topic/navigation-post.tpl new file mode 100644 index 0000000..55140e0 --- /dev/null +++ b/templates/partials/topic/navigation-post.tpl @@ -0,0 +1,12 @@ +<div class="clearfix"> + <div class="icon float-start"> + <a href="<!-- IF post.user.remoteId -->{config.relative_path}/user/{post.user.remoteId}<!-- ELSE -->#<!-- ENDIF post.user.remoteId -->"> + {buildAvatar(post.user, "24px", true, "", "user/picture")} {post.user.username} + </a> + </div> + <small class="float-end"> + <span class="timeago" title="{post.timestampISO}"></span> + </small> +</div> + +<div>{post.content}</div> \ No newline at end of file diff --git a/templates/partials/topic/post-editor.tpl b/templates/partials/topic/post-editor.tpl new file mode 100644 index 0000000..eca7e08 --- /dev/null +++ b/templates/partials/topic/post-editor.tpl @@ -0,0 +1 @@ +<small data-editor="{editor.remoteId}" component="post/editor" class="hidden">[[global:last-edited-by, {editor.username}]] <span class="timeago" title="{isoTimeToLocaleString(editedISO, config.userLang)}"></span></small> \ No newline at end of file diff --git a/templates/partials/topic/post.tpl b/templates/partials/topic/post.tpl new file mode 100644 index 0000000..93c3b93 --- /dev/null +++ b/templates/partials/topic/post.tpl @@ -0,0 +1,140 @@ +{{{ if (!./index && widgets.mainpost-header.length) }}} +<div data-widget-area="mainpost-header"> + {{{ each widgets.mainpost-header }}} + {widgets.mainpost-header.html} + {{{ end }}} +</div> +{{{ end }}} + +<div class="clearfix post-header"> + <div class="icon float-start"> + <a href="<!-- IF posts.user.remoteId -->{config.relative_path}/user/{posts.user.remoteId}<!-- ELSE -->#<!-- ENDIF posts.user.remoteId -->"> + {buildAvatar(posts.user, "48px", true, "", "user/picture")} + {{{ if ./user.isLocal }}} + <span component="user/status" class="position-absolute top-100 start-100 border border-white border-2 rounded-circle status {posts.user.status}"><span class="visually-hidden">[[global:{posts.user.status}]]</span></span> + {{{ else }}} + <span component="user/locality" class="position-absolute top-100 start-100 lh-1 border border-white border-2 rounded-circle small" title="[[global:remote-user]]"> + <span class="visually-hidden">[[global:remote-user]]</span> + <i class="fa fa-globe"></i> + </span> + {{{ end }}} + </a> + </div> + + <small class="d-flex"> + <div class="d-flex align-items-center gap-1 flex-wrap w-100"> + <strong class="text-nowrap" itemprop="author" itemscope itemtype="https://schema.org/Person"> + <meta itemprop="name" content="{./user.username}"> + {{{ if ./user.remoteId }}}<meta itemprop="url" content="{config.relative_path}/user/{./user.remoteId}">{{{ end }}} + <a href="<!-- IF posts.user.remoteId -->{config.relative_path}/user/{posts.user.remoteId}<!-- ELSE -->#<!-- ENDIF posts.user.remoteId -->" data-username="{posts.user.username}" data-uid="{posts.user.uid}">{posts.user.displayname}</a> + </strong> + + {{{ each posts.user.selectedGroups }}} + {{{ if posts.user.selectedGroups.slug }}} + <!-- IMPORT partials/groups/badge.tpl --> + {{{ end }}} + {{{ end }}} + + <!-- IF posts.user.banned --> + <span class="badge bg-danger">[[user:banned]]</span> + <!-- ENDIF posts.user.banned --> + + <span class="visible-xs-inline-block visible-sm-inline-block visible-md-inline-block visible-lg-inline-block"> + {{{ if posts.toPid }}} + <a component="post/parent" class="btn btn-sm btn-ghost py-0 px-1 text-xs hidden-xs" data-topid="{posts.toPid}" href="{config.relative_path}/post/{posts.toPid}"><i class="fa fa-reply"></i> @{{{ if posts.parent.user.remoteId }}}{posts.parent.user.username}{{{ else }}}[[global:guest]]{{{ end }}}</a> + {{{ end }}} + + <span> + <!-- IF posts.user.custom_profile_info.length --> + | + {{{each posts.user.custom_profile_info}}} + {posts.user.custom_profile_info.content} + {{{end}}} + <!-- ENDIF posts.user.custom_profile_info.length --> + </span> + </span> + <div class="d-flex align-items-center gap-1 flex-grow-1 justify-content-end"> + <span> + <i component="post/edit-indicator" class="fa fa-pencil-square<!-- IF privileges.posts:history --> pointer<!-- END --> edit-icon <!-- IF !posts.editor.username -->hidden<!-- ENDIF !posts.editor.username -->"></i> + + <span data-editor="{posts.editor.remoteId}" component="post/editor" class="hidden">[[global:last-edited-by, {posts.editor.username}]] <span class="timeago" title="{isoTimeToLocaleString(posts.editedISO, config.userLang)}"></span></span> + + <span class="visible-xs-inline-block visible-sm-inline-block visible-md-inline-block visible-lg-inline-block"> + <a class="permalink text-muted" href="{config.relative_path}/post/{posts.pid}"><span class="timeago" title="{posts.timestampISO}"></span></a> + </span> + </span> + <span class="bookmarked"><i class="fa fa-bookmark-o"></i></span> + </div> + </div> + </small> +</div> + +<br /> + +<div class="content" component="post/content" itemprop="text"> + {posts.content} +</div> + +<div class="post-footer"> + {{{ if posts.user.signature }}} + <div component="post/signature" data-uid="{posts.user.uid}" class="post-signature">{posts.user.signature}</div> + {{{ end }}} + + <div class="clearfix"> + {{{ if !hideReplies }}} + <a component="post/reply-count" data-target-component="post/replies/container" href="#" class="threaded-replies user-select-none float-start text-muted {{{ if (!./replies || shouldHideReplyContainer(@value)) }}}hidden{{{ end }}}"> + <span component="post/reply-count/avatars" class="avatars d-inline-flex gap-1 align-items-top hidden-xs {{{ if posts.replies.hasMore }}}hasMore{{{ end }}}"> + {{{each posts.replies.users}}} + <span>{buildAvatar(posts.replies.users, "16px", true, "")}</span> + {{{end}}} + {{{ if posts.replies.hasMore}}} + <span><i class="fa fa-ellipsis"></i></span> + {{{ end }}} + </span> + + <span class="replies-count small" component="post/reply-count/text" data-replies="{posts.replies.count}">{posts.replies.text}</span> + <span class="replies-last hidden-xs small">[[topic:last-reply-time]] <span class="timeago" title="{posts.replies.timestampISO}"></span></span> + + <i class="fa fa-fw fa-chevron-down" component="post/replies/open"></i> + </a> + {{{ end }}} + + <small class="d-flex justify-content-end align-items-center gap-1" component="post/actions"> + <!-- IMPORT partials/topic/reactions.tpl --> + <span class="post-tools"> + <a component="post/reply" href="#" class="btn btn-sm btn-link user-select-none <!-- IF !privileges.topics:reply -->hidden<!-- ENDIF !privileges.topics:reply -->">[[topic:reply]]</a> + <a component="post/quote" href="#" class="btn btn-sm btn-link user-select-none <!-- IF !privileges.topics:reply -->hidden<!-- ENDIF !privileges.topics:reply -->">[[topic:quote]]</a> + </span> + + {{{ if ./announces }}} + <a component="post/announce-count" href="#" class="btn-ghost-sm" title="[[topic:announcers]]"><i class="fa fa-share-alt text-primary"></i> {./announces}</a> + {{{ end }}} + + <!-- IF !reputation:disabled --> + <span class="votes"> + <a component="post/upvote" href="#" class="btn btn-sm btn-link <!-- IF posts.upvoted -->upvoted<!-- ENDIF posts.upvoted -->"> + <i class="fa fa-chevron-up"></i> + </a> + + <span class="btn btn-sm btn-link" component="post/vote-count" data-votes="{posts.votes}">{posts.votes}</span> + + <!-- IF !downvote:disabled --> + <a component="post/downvote" href="#" class="btn btn-sm btn-link <!-- IF posts.downvoted -->downvoted<!-- ENDIF posts.downvoted -->"> + <i class="fa fa-chevron-down"></i> + </a> + <!-- ENDIF !downvote:disabled --> + </span> + <!-- ENDIF !reputation:disabled --> + + <!-- IMPORT partials/topic/post-menu.tpl --> + </small> + </div> + <div component="post/replies/container"></div> +</div> +{{{ if (!./index && widgets.mainpost-footer.length) }}} +<div data-widget-area="mainpost-footer"> + {{{ each widgets.mainpost-footer }}} + {widgets.mainpost-footer.html} + {{{ end }}} +</div> +{{{ end }}} \ No newline at end of file diff --git a/templates/partials/topic/quickreply.tpl b/templates/partials/topic/quickreply.tpl new file mode 100644 index 0000000..e5e362c --- /dev/null +++ b/templates/partials/topic/quickreply.tpl @@ -0,0 +1,27 @@ +{{{ if privileges.topics:reply }}} +<div component="topic/quickreply/container" class="quick-reply d-flex gap-3 mb-4"> + <div class="icon hidden-xs"> + <a class="d-inline-block position-relative" href="{{{ if loggedInUser.remoteId }}}{config.relative_path}/user/{loggedInUser.remoteId}{{{ else }}}#{{{ end }}}"> + {buildAvatar(loggedInUser, "48px", true, "", "user/picture")} + </a> + </div> + <form class="flex-grow-1 d-flex flex-column gap-2" method="post" action="{config.relative_path}/compose"> + <input type="hidden" name="tid" value="{tid}" /> + <input type="hidden" name="_csrf" value="{config.csrf_token}" /> + <div class="quickreply-message position-relative"> + <textarea rows="4" name="content" component="topic/quickreply/text" class="form-control mousetrap" placeholder="[[modules:composer.textarea.placeholder]]"></textarea> + <div class="imagedrop"><div>[[topic:composer.drag-and-drop-images]]</div></div> + </div> + <div> + <div class="d-flex justify-content-end gap-2"> + <button type="button" component="topic/quickreply/upload/button" class="btn btn-ghost btn-sm border"><i class="fa fa-upload"></i></button> + <button type="button" component="topic/quickreply/expand" class="btn btn-ghost btn-sm border" title="[[topic:open-composer]]"><i class="fa fa-expand"></i></button> + <button type="submit" component="topic/quickreply/button" class="btn btn-sm btn-primary">[[topic:post-quick-reply]]</button> + </div> + </div> + </form> + <form class="d-none" component="topic/quickreply/upload" method="post" enctype="multipart/form-data"> + <input type="file" name="files[]" multiple class="hidden"/> + </form> +</div> +{{{ end }}} diff --git a/templates/partials/topics_list.tpl b/templates/partials/topics_list.tpl new file mode 100644 index 0000000..061a3e6 --- /dev/null +++ b/templates/partials/topics_list.tpl @@ -0,0 +1,131 @@ +<ul component="category" class="topics-list list-unstyled" itemscope itemtype="http://www.schema.org/ItemList" data-nextstart="{nextStart}" data-set="{set}"> + + {{{ each topics }}} + <li component="category/topic" class="category-item hover-parent py-2 mb-2 d-flex flex-column flex-lg-row align-items-start {function.generateTopicClass}" <!-- IMPORT partials/data/category.tpl -->> + <link itemprop="url" content="{config.relative_path}/topic/{./slug}" /> + <meta itemprop="name" content="{function.stripTags, ./title}" /> + <meta itemprop="itemListOrder" content="descending" /> + <meta itemprop="position" content="{increment(./index, "1")}" /> + <a id="{./index}" data-index="{./index}" component="topic/anchor"></a> + + <div class="d-flex p-0 col-12 col-lg-7 gap-2 gap-lg-3 pe-1 align-items-start {{{ if config.theme.mobileTopicTeasers }}}mb-2 mb-lg-0{{{ end }}}"> + <div class="flex-shrink-0 position-relative"> + <a class="d-inline-block text-decoration-none avatar-tooltip" title="{./user.displayname}" href="{{{ if ./user.remoteId }}}{config.relative_path}/user/{./user.remoteId}{{{ else }}}#{{{ end }}}"> + {buildAvatar(./user, "40px", true)} + </a> + {{{ if showSelect }}} + <div class="checkbox position-absolute top-100 start-50 translate-middle-x m-0 d-none d-lg-flex" style="max-width:max-content"> + <i component="topic/select" class="fa text-muted pointer fa-square-o p-1 hover-visible"></i> + </div> + {{{ end }}} + </div> + <div class="flex-grow-1 d-flex flex-wrap gap-1 position-relative"> + <h3 component="topic/header" class="title text-break fs-5 fw-semibold m-0 tracking-tight w-100 {{{ if showSelect }}}me-4 me-lg-0{{{ end }}}"> + <a class="text-reset" href="{{{ if topics.noAnchor }}}#{{{ else }}}{config.relative_path}/topic/{./slug}{{{ if ./bookmark }}}/{./bookmark}{{{ end }}}{{{ end }}}">{./title}</a> + </h3> + <div component="topic/labels" class="d-flex flex-wrap gap-1 w-100 align-items-center"> + <span component="topic/watched" class="badge border border-gray-300 text-body {{{ if !./followed }}}hidden{{{ end }}}"> + <i class="fa fa-bell-o"></i> + <span>[[topic:watching]]</span> + </span> + <span component="topic/ignored" class="badge border border-gray-300 text-body {{{ if !./ignored }}}hidden{{{ end }}}"> + <i class="fa fa-eye-slash"></i> + <span>[[topic:ignoring]]</span> + </span> + <span component="topic/scheduled" class="badge border border-gray-300 text-body {{{ if !./scheduled }}}hidden{{{ end }}}"> + <i class="fa fa-clock-o"></i> + <span>[[topic:scheduled]]</span> + </span> + <span component="topic/pinned" class="badge border border-gray-300 text-body {{{ if (./scheduled || !./pinned) }}}hidden{{{ end }}}"> + <i class="fa fa-thumb-tack"></i> + <span>{{{ if !./pinExpiry }}}[[topic:pinned]]{{{ else }}}[[topic:pinned-with-expiry, {isoTimeToLocaleString(./pinExpiryISO, config.userLang)}]]{{{ end }}}</span> + </span> + <span component="topic/locked" class="badge border border-gray-300 text-body {{{ if !./locked }}}hidden{{{ end }}}"> + <i class="fa fa-lock"></i> + <span>[[topic:locked]]</span> + </span> + <span component="topic/moved" class="badge border border-gray-300 text-body {{{ if !./oldCid }}}hidden{{{ end }}}"> + <i class="fa fa-arrow-circle-right"></i> + <span>[[topic:moved]]</span> + </span> + {{{each ./icons}}}<span class="lh-1">{@value}</span>{{{end}}} + + {{{ if !template.category }}} + {function.buildCategoryLabel, ./category, "a", "border"} + {{{ end }}} + + <span data-tid="{./tid}" component="topic/tags" class="lh-1 tag-list d-flex flex-wrap gap-1 {{{ if !./tags.length }}}hidden{{{ end }}}"> + {{{ each ./tags }}} + <a href="{config.relative_path}/tags/{./valueEncoded}"><span class="badge border border-gray-300 fw-normal tag tag-class-{./class}" data-tag="{./value}">{./valueEscaped}</span></a> + {{{ end }}} + </span> + + <div class="d-flex gap-1 d-block d-lg-none w-100"> + <span class="badge text-body border stats text-xs text-muted"> + <i class="fa-regular fa-fw fa-message"></i> + <span component="topic/post-count" class="fw-normal">{humanReadableNumber(./postcount, 0)}</span> + </span> + + <a href="{config.relative_path}/topic/{./slug}{{{ if (./teaser.timestampISO && !config.theme.mobileTopicTeasers) }}}/{./teaser.index}{{{ end }}}" class="border badge bg-transparent text-muted fw-normal timeago" title="{{{ if (./teaser.timestampISO && !config.theme.mobileTopicTeasers) }}}{./teaser.timestampISO}{{{ else }}}{./timestampISO}{{{ end }}}"></a> + </div> + + <a href="{config.relative_path}/topic/{./slug}" class="d-none d-lg-block badge bg-transparent text-muted fw-normal timeago" title="{./timestampISO}"></a> + </div> + {{{ if showSelect }}} + <div class="checkbox position-absolute top-0 end-0 m-0 d-flex d-lg-none" style="max-width:max-content"> + <i component="topic/select" class="fa fa-square-o text-muted pointer p-1"></i> + </div> + {{{ end }}} + </div> + {{{ if ./thumbs.length }}} + <a class="topic-thumbs position-relative text-decoration-none flex-shrink-0 d-none d-xl-block" href="{config.relative_path}/topic/{./slug}{{{ if ./bookmark }}}/{./bookmark}{{{ end }}}" aria-label="[[topic:thumb-image]]"> + <img class="topic-thumb rounded-1 bg-light" style="width:auto;max-width: 5.33rem;height: 3.33rem;object-fit: contain;" src="{./thumbs.0.url}" alt=""/> + <span data-numthumbs="{./thumbs.length}" class="px-1 position-absolute bottom-0 end-0 badge rounded-0 border fw-semibold text-bg-light" style="z-index: 1; border-top-left-radius: 0.25rem!important; border-bottom-right-radius: 0.25rem!important;">{./thumbs.length}</span> + </a> + {{{ end }}} + </div> + + <div class="d-flex p-0 col-lg-5 col-12 align-content-stretch"> + <div class="meta stats d-none d-lg-grid col-6 gap-1 pe-2 text-muted" style="grid-template-columns: 1fr 1fr 1fr;"> + {{{ if !reputation:disabled }}} + <div class="stats-votes overflow-hidden d-flex flex-column align-items-center"> + <span class="fs-4" title="{./votes}">{humanReadableNumber(./votes, 0)}</span> + <span class="d-none d-xl-flex text-uppercase text-xs">[[global:votes]]</span> + <i class="d-xl-none fa fa-fw text-xs text-muted opacity-75 fa-chevron-up"></i> + </div> + {{{ end }}} + <div class="stats-postcount overflow-hidden d-flex flex-column align-items-center"> + <span class="fs-4" title="{./postcount}">{humanReadableNumber(./postcount, 0)}</span> + <span class="d-none d-xl-flex text-uppercase text-xs">[[global:posts]]</span> + <i class="d-xl-none fa-regular fa-fw text-xs text-muted opacity-75 fa-message"></i> + </div> + <div class="stats-viewcount overflow-hidden d-flex flex-column align-items-center"> + <span class="fs-4" title="{./viewcount}">{humanReadableNumber(./viewcount, 0)}</span> + <span class="d-none d-xl-flex text-uppercase text-xs">[[global:views]]</span> + <i class="d-xl-none fa fa-fw text-xs text-muted opacity-75 fa-eye"></i> + </div> + </div> + <div component="topic/teaser" class="meta teaser col-lg-6 col-12 {{{ if !config.theme.mobileTopicTeasers }}}d-none d-lg-block{{{ end }}}"> + <div class="lastpost border-start border-4 lh-sm h-100 d-flex flex-column gap-1" style="border-color: {./category.bgColor}!important;"> + {{{ if ./unreplied }}} + <div class="ps-2 text-xs"> + [[category:no-replies]] + </div> + {{{ else }}} + {{{ if ./teaser.pid }}} + <div class="ps-2"> + <a href="{{{ if ./teaser.user.remoteId }}}{config.relative_path}/user/{./teaser.user.remoteId}{{{ else }}}#{{{ end }}}" class="text-decoration-none avatar-tooltip" title="{./teaser.user.displayname}">{buildAvatar(./teaser.user, "18px", true)}</a> + <a class="permalink text-muted timeago text-xs" href="{config.relative_path}/topic/{./slug}/{./teaser.index}" title="{./teaser.timestampISO}" aria-label="[[global:lastpost]]"></a> + </div> + <div class="post-content text-xs ps-2 line-clamp-sm-2 lh-sm text-break position-relative flex-fill"> + <a class="stretched-link" tabindex="-1" href="{config.relative_path}/topic/{./slug}/{./teaser.index}" aria-label="[[global:lastpost]]"></a> + {./teaser.content} + </div> + {{{ end }}} + {{{ end }}} + </div> + </div> + </div> + </li> + {{{end}}} +</ul> diff --git a/templates/partials/users/item.tpl b/templates/partials/users/item.tpl new file mode 100644 index 0000000..6afea6b --- /dev/null +++ b/templates/partials/users/item.tpl @@ -0,0 +1,41 @@ +<li class="users-box registered-user text-center pb-3" data-uid="{users.uid}" style="width: 102px;"> + <a href="{config.relative_path}/user/{users.remoteId}">{buildAvatar(users, "64px", true)}</a> + + <div class="user-info"> + <div class="text-nowrap text-truncate"> + <a href="{config.relative_path}/user/{users.remoteId}">{users.username}</a> + </div> + <!-- IF section_online --> + <div class="lastonline"> + <span class="timeago" title="{users.lastonlineISO}"></span> + </div> + <!-- ENDIF section_online --> + + <!-- IF section_joindate --> + <div class="joindate"> + <span class="timeago" title="{users.joindateISO}"></span> + </div> + <!-- ENDIF section_joindate --> + + <!-- IF section_sort-reputation --> + <div class="reputation"> + <i class="fa fa-star"></i> + <span>{formattedNumber(users.reputation)}</span> + </div> + <!-- ENDIF section_sort-reputation --> + + <!-- IF section_sort-posts --> + <div class="post-count"> + <i class="fa fa-pencil"></i> + <span>{formattedNumber(users.postcount)}</span> + </div> + <!-- ENDIF section_sort-posts --> + + <!-- IF section_flagged --> + <div class="flag-count"> + <i class="fa fa-flag"></i> + <span><a href="{config.relative_path}/flags?targetUid={users.uid}">{users.flags}</a></span> + </div> + <!-- ENDIF section_flagged --> + </div> +</li> \ No newline at end of file