mirror of
https://github.com/halo-dev/theme-earth.git
synced 2025-10-14 14:30:50 +00:00
feat: add moments page (#112)
适配[瞬间插件](https://github.com/halo-sigs/plugin-moments)。 /kind feature Fixes https://github.com/halo-dev/theme-earth/issues/70 <img width="1138" alt="image" src="https://github.com/halo-dev/theme-earth/assets/21301288/6fc9df4b-0fb3-4116-8cb9-2e02242acbcc"> <img width="1036" alt="image" src="https://github.com/halo-dev/theme-earth/assets/21301288/e4d3243b-e35c-41f5-a1d6-86c1ed9d8b85"> ```release-note 适配瞬间插件。 ```
This commit is contained in:
@@ -11,4 +11,7 @@ module.exports = {
|
||||
env: {
|
||||
node: true,
|
||||
},
|
||||
rules: {
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
},
|
||||
};
|
||||
|
@@ -55,6 +55,7 @@
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-tailwindcss": "^0.3.0",
|
||||
"release-it": "^15.11.0",
|
||||
"sass": "^1.69.3",
|
||||
"tailwindcss": "^3.3.2",
|
||||
"tailwindcss-plugin-icons": "^2.1.1",
|
||||
"typescript": "^4.9.5",
|
||||
|
22
pnpm-lock.yaml
generated
22
pnpm-lock.yaml
generated
@@ -67,6 +67,9 @@ devDependencies:
|
||||
release-it:
|
||||
specifier: ^15.11.0
|
||||
version: 15.11.0
|
||||
sass:
|
||||
specifier: ^1.69.3
|
||||
version: 1.69.3
|
||||
tailwindcss:
|
||||
specifier: ^3.3.2
|
||||
version: 3.3.2
|
||||
@@ -78,7 +81,7 @@ devDependencies:
|
||||
version: 4.9.5
|
||||
vite:
|
||||
specifier: ^4.3.9
|
||||
version: 4.3.9(@types/node@18.11.9)
|
||||
version: 4.3.9(@types/node@18.11.9)(sass@1.69.3)
|
||||
|
||||
packages:
|
||||
|
||||
@@ -2113,6 +2116,10 @@ packages:
|
||||
engines: {node: '>= 4'}
|
||||
dev: true
|
||||
|
||||
/immutable@4.3.4:
|
||||
resolution: {integrity: sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==}
|
||||
dev: true
|
||||
|
||||
/import-fresh@3.3.0:
|
||||
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
|
||||
engines: {node: '>=6'}
|
||||
@@ -3459,6 +3466,16 @@ packages:
|
||||
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||
dev: true
|
||||
|
||||
/sass@1.69.3:
|
||||
resolution: {integrity: sha512-X99+a2iGdXkdWn1akFPs0ZmelUzyAQfvqYc2P/MPTrJRuIRoTffGzT9W9nFqG00S+c8hXzVmgxhUuHFdrwxkhQ==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
hasBin: true
|
||||
dependencies:
|
||||
chokidar: 3.5.3
|
||||
immutable: 4.3.4
|
||||
source-map-js: 1.0.2
|
||||
dev: true
|
||||
|
||||
/semver-diff@4.0.0:
|
||||
resolution: {integrity: sha512-0Ju4+6A8iOnpL/Thra7dZsSlOHYAHIeMxfhWQRI1/VLcT3WDBZKKtQt/QkBOsiIN9ZpuvHE6cGZ0x4glCMmfiA==}
|
||||
engines: {node: '>=12'}
|
||||
@@ -3908,7 +3925,7 @@ packages:
|
||||
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
|
||||
dev: true
|
||||
|
||||
/vite@4.3.9(@types/node@18.11.9):
|
||||
/vite@4.3.9(@types/node@18.11.9)(sass@1.69.3):
|
||||
resolution: {integrity: sha512-qsTNZjO9NoJNW7KnOrgYwczm0WctJ8m/yqYAMAK9Lxt4SoySUfS5S8ia9K7JHpa3KEeMfyF8LoJ3c5NeBJy6pg==}
|
||||
engines: {node: ^14.18.0 || >=16.0.0}
|
||||
hasBin: true
|
||||
@@ -3937,6 +3954,7 @@ packages:
|
||||
esbuild: 0.17.12
|
||||
postcss: 8.4.24
|
||||
rollup: 3.26.0
|
||||
sass: 1.69.3
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
dev: true
|
||||
|
@@ -1,14 +1,14 @@
|
||||
interface PostUpvote {
|
||||
interface upvoteState {
|
||||
upvotedNames: string[];
|
||||
init(): void;
|
||||
upvoted(id: string): boolean;
|
||||
handleUpvote(name: string): void;
|
||||
}
|
||||
|
||||
export default (): PostUpvote => ({
|
||||
export default (key: string, group: string, plural: string): upvoteState => ({
|
||||
upvotedNames: [],
|
||||
init() {
|
||||
this.upvotedNames = JSON.parse(localStorage.getItem("halo.upvoted.post.names") || "[]");
|
||||
this.upvotedNames = JSON.parse(localStorage.getItem(`halo.upvoted.${key}.names`) || "[]");
|
||||
},
|
||||
upvoted(id: string) {
|
||||
return this.upvotedNames.includes(id);
|
||||
@@ -25,24 +25,24 @@ export default (): PostUpvote => ({
|
||||
|
||||
xhr.onload = () => {
|
||||
this.upvotedNames = [...this.upvotedNames, name];
|
||||
localStorage.setItem("halo.upvoted.post.names", JSON.stringify(this.upvotedNames));
|
||||
localStorage.setItem(`halo.upvoted.${key}.names`, JSON.stringify(this.upvotedNames));
|
||||
|
||||
const upvoteNode = document.querySelector('[data-upvote-post-name="' + name + '"]');
|
||||
const upvoteNode = document.querySelector("[data-upvote-" + key + '-name="' + name + '"]');
|
||||
|
||||
if (!upvoteNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
const upvoteCount = parseInt(upvoteNode.textContent || "0");
|
||||
upvoteNode.textContent = upvoteCount + 1 + " 点赞";
|
||||
upvoteNode.textContent = upvoteCount + 1 + "";
|
||||
};
|
||||
xhr.onerror = function () {
|
||||
alert("网络请求失败,请稍后再试");
|
||||
};
|
||||
xhr.send(
|
||||
JSON.stringify({
|
||||
group: "content.halo.run",
|
||||
plural: "posts",
|
||||
group: group,
|
||||
plural: plural,
|
||||
name: name,
|
||||
})
|
||||
);
|
@@ -1,16 +1,17 @@
|
||||
import "./styles/tailwind.css";
|
||||
import "./styles/main.css";
|
||||
import "./styles/main.scss";
|
||||
import Alpine from "alpinejs";
|
||||
import * as tocbot from "tocbot";
|
||||
import dropdown from "./alpine-data/dropdown";
|
||||
import colorSchemeSwitcher from "./alpine-data/color-scheme-switcher";
|
||||
import postUpvote from "./alpine-data/post-upvote";
|
||||
import upvote from "./alpine-data/upvote";
|
||||
|
||||
window.Alpine = Alpine;
|
||||
|
||||
Alpine.data("dropdown", dropdown);
|
||||
Alpine.data("colorSchemeSwitcher", colorSchemeSwitcher);
|
||||
Alpine.data("postUpvote", postUpvote);
|
||||
// @ts-ignore
|
||||
Alpine.data("upvote", upvote);
|
||||
|
||||
Alpine.start();
|
||||
|
||||
|
@@ -41,3 +41,12 @@ body {
|
||||
.is-active-li {
|
||||
@apply rounded bg-gray-100 dark:bg-slate-600;
|
||||
}
|
||||
|
||||
.moment-content {
|
||||
.tag {
|
||||
&::before {
|
||||
content: "#";
|
||||
}
|
||||
@apply rounded bg-gray-100 px-1 py-0.5 text-sm text-gray-900 hover:bg-gray-200 dark:bg-slate-600 dark:text-slate-50 dark:hover:bg-slate-700 dark:hover:text-slate-100;
|
||||
}
|
||||
}
|
4
templates/assets/dist/main.iife.js
vendored
4
templates/assets/dist/main.iife.js
vendored
File diff suppressed because one or more lines are too long
2
templates/assets/dist/style.css
vendored
2
templates/assets/dist/style.css
vendored
File diff suppressed because one or more lines are too long
180
templates/moments.html
Normal file
180
templates/moments.html
Normal file
@@ -0,0 +1,180 @@
|
||||
<!DOCTYPE html>
|
||||
<html
|
||||
xmlns:th="https://www.thymeleaf.org"
|
||||
th:replace="~{modules/layout :: html(title = '瞬间 - ' + ${site.title}, hero = null, content = ~{::content}, head = null, footer = null, sidebar = ~{::sidebar}, contentClass = '')}"
|
||||
>
|
||||
<th:block th:fragment="content">
|
||||
<div class="rounded-xl bg-white p-4 dark:bg-slate-800">
|
||||
<h1 class="mb-9 text-2xl font-medium dark:text-slate-50">瞬间</h1>
|
||||
<div class="mb-8">
|
||||
<nav class="flex flex-wrap gap-4" aria-label="Tabs">
|
||||
<a
|
||||
th:href="@{/moments}"
|
||||
class="rounded bg-gray-100 px-1 py-0.5 text-sm text-gray-900 hover:bg-gray-200 dark:bg-slate-600 dark:text-slate-50 dark:hover:bg-slate-700 dark:hover:text-slate-100"
|
||||
th:classappend="${#lists.isEmpty(param.tag) ? '!bg-gray-200 dark:!bg-slate-700 dark:!text-slate-100 ring-2 ring-gray-300 dark:ring-slate-600' : ''}"
|
||||
>
|
||||
<span>全部</span>
|
||||
</a>
|
||||
<a
|
||||
th:each="tag : ${tags}"
|
||||
th:href="@{|?tag=${tag.name}|}"
|
||||
class="rounded bg-gray-100 px-1 py-0.5 text-sm text-gray-900 hover:bg-gray-200 dark:bg-slate-600 dark:text-slate-50 dark:hover:bg-slate-700 dark:hover:text-slate-100"
|
||||
th:classappend="${#lists.contains(param.tag, tag.name) ? '!bg-gray-200 dark:!bg-slate-700 dark:!text-slate-100 ring-2 ring-gray-300 dark:ring-slate-600' : ''}"
|
||||
>
|
||||
<span th:text="|#${tag.name}|"></span>
|
||||
<sup th:text="${tag.momentCount}"></sup>
|
||||
</a>
|
||||
</nav>
|
||||
</div>
|
||||
<div>
|
||||
<ul
|
||||
role="list"
|
||||
class="divide-y divide-gray-100 dark:divide-slate-700"
|
||||
x-data="upvote('moment','moment.halo.run','moments')"
|
||||
>
|
||||
<li
|
||||
th:each="moment : ${moments.items}"
|
||||
th:attr="x-data=|{name:'${moment.metadata.name}',showComment:false}|"
|
||||
th:with="content=${moment.spec.content}"
|
||||
class="animated fadeIn flex w-full items-start gap-2 py-5"
|
||||
>
|
||||
<img class="h-12 w-12 rounded-full" th:src="${moment.owner.avatar}" th:alt="${moment.owner.displayName}" />
|
||||
<div class="ml-6" style="width: calc(100% - 4.75rem)">
|
||||
<div
|
||||
th:utext="${content.html}"
|
||||
class="moment-content prose prose-base !max-w-none break-words dark:prose-invert prose-pre:p-0"
|
||||
></div>
|
||||
<div
|
||||
th:unless="${#lists.isEmpty(moment.spec.content.medium)}"
|
||||
class="moment-media mt-4 grid w-full grid-cols-3 gap-2 sm:w-1/2"
|
||||
>
|
||||
<div class="aspect-h-1 aspect-w-1" th:each="media : ${moment.spec.content.medium}">
|
||||
<img
|
||||
th:if="${#strings.equals(media.type,'PHOTO')}"
|
||||
class="transform-gpu rounded-lg object-cover"
|
||||
th:src="${media.url}"
|
||||
/>
|
||||
<div
|
||||
th:if="${#strings.equals(media.type,'VIDEO')}"
|
||||
x-data="{openVideoModal:false}"
|
||||
class="aspect-h-1 aspect-w-1"
|
||||
>
|
||||
<video th:src="${media.url}" class="rounded-lg object-cover"></video>
|
||||
<div
|
||||
th:if="${#strings.equals(media.type,'VIDEO')}"
|
||||
class="absolute flex h-full w-full cursor-pointer items-center justify-center rounded-lg bg-gray-50 opacity-50 transition-all hover:opacity-70"
|
||||
@click="openVideoModal = true"
|
||||
>
|
||||
<i class="i-tabler-play !h-7 !w-7"></i>
|
||||
</div>
|
||||
<template x-teleport="body">
|
||||
<div>
|
||||
<div
|
||||
class="fixed inset-0 z-50 bg-gray-800/40 opacity-100 backdrop-blur-sm"
|
||||
aria-hidden="true"
|
||||
x-show="openVideoModal"
|
||||
x-transition:enter="ease-in-out duration-300"
|
||||
x-transition:enter-start="opacity-0"
|
||||
x-transition:enter-end="opacity-100"
|
||||
x-transition:leave="ease-in-out duration-300"
|
||||
x-transition:leave-start="opacity-100"
|
||||
x-transition:leave-end="opacity-0"
|
||||
></div>
|
||||
<div
|
||||
class="fixed inset-0 z-50 overflow-y-auto"
|
||||
tabindex="-1"
|
||||
x-show="openVideoModal"
|
||||
x-transition:enter="ease-out duration-200"
|
||||
x-transition:enter-start="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
x-transition:enter-end="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave="ease-in duration-100"
|
||||
x-transition:leave-start="opacity-100 translate-y-0 sm:scale-100"
|
||||
x-transition:leave-end="opacity-0 translate-y-4 sm:translate-y-0 sm:scale-95"
|
||||
>
|
||||
<div class="flex min-h-full flex-col items-center justify-center p-4 text-center sm:p-0">
|
||||
<div
|
||||
@click.outside="openVideoModal = false"
|
||||
class="relative my-4 transform overflow-hidden rounded-lg bg-white text-left shadow-xl transition-all sm:w-full sm:max-w-xl"
|
||||
>
|
||||
<video th:src="${media.url}" class="w-full" controls></video>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
@click="openVideoModal = false"
|
||||
class="group inline-flex items-center justify-center rounded-full bg-white p-1.5"
|
||||
>
|
||||
<i class="i-tabler-x block text-gray-600 group-hover:text-gray-900"></i>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex items-center gap-4">
|
||||
<div
|
||||
class="inline-flex cursor-pointer items-center text-sm text-gray-400 transition-all hover:text-red-700 dark:text-slate-400 dark:hover:text-red-700"
|
||||
x-bind:class="{'!text-red-700': upvoted(name)}"
|
||||
@click="handleUpvote(name)"
|
||||
>
|
||||
<i x-show="upvoted(name)" class="i-tabler-heart-filled h-3 w-3"></i>
|
||||
<i x-show="!upvoted(name)" class="i-tabler-heart h-3 w-3"></i>
|
||||
<span
|
||||
class="ml-1"
|
||||
th:attr="data-upvote-moment-name=${moment.metadata.name}"
|
||||
th:text="${moment.stats.upvote}"
|
||||
>
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
class="inline-flex cursor-pointer items-center text-sm text-gray-400 transition-all hover:text-black dark:text-slate-400 dark:hover:text-slate-300"
|
||||
:class="{'!text-black dark:!text-slate-300':showComment}"
|
||||
x-on:click="showComment = !showComment"
|
||||
>
|
||||
<i class="i-tabler-message-circle h-3 w-3"></i>
|
||||
<span class="ml-1" th:text="${moment.stats.approvedComment}"> </span>
|
||||
</div>
|
||||
<div class="inline-flex items-center text-sm text-gray-400 transition-all dark:text-slate-400">
|
||||
<i class="i-tabler-calendar h-3 w-3"></i>
|
||||
<span class="ml-1" th:text="${#dates.format(moment.spec.releaseTime,'yyyy-MM-dd')}"> </span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-2" x-show="showComment">
|
||||
<halo:comment
|
||||
group="moment.halo.run"
|
||||
kind="Moment"
|
||||
th:attr="name=${moment.metadata.name}"
|
||||
colorScheme="window.main.currentColorScheme"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="mt-6 flex items-center justify-between" th:if="${moments.hasPrevious() || moments.hasNext()}">
|
||||
<a
|
||||
th:href="@{${moments.prevUrl}}"
|
||||
class="whitespace-no-wrap group inline-flex items-center justify-center gap-1 rounded-md border border-gray-200 bg-white px-4 py-1 text-sm font-medium leading-6 text-gray-600 shadow-sm hover:bg-gray-50 focus:shadow-none focus:outline-none dark:border-slate-600 dark:bg-slate-700 dark:text-slate-100 dark:hover:bg-slate-600 dark:hover:text-white"
|
||||
>
|
||||
<span class="i-tabler-arrow-left text-lg transition-all group-hover:-translate-x-1"></span>
|
||||
<span>上一页</span>
|
||||
</a>
|
||||
<span
|
||||
class="text-sm text-gray-900 dark:text-slate-50"
|
||||
th:text="|${moments.page} / ${moments.totalPages}|"
|
||||
></span>
|
||||
<a
|
||||
th:href="@{${moments.nextUrl}}"
|
||||
class="whitespace-no-wrap group inline-flex items-center justify-center gap-1 rounded-md border border-gray-200 bg-white px-4 py-1 text-sm font-medium leading-6 text-gray-600 shadow-sm hover:bg-gray-50 focus:shadow-none focus:outline-none dark:border-slate-600 dark:bg-slate-700 dark:text-slate-100 dark:hover:bg-slate-600 dark:hover:text-white"
|
||||
>
|
||||
<span>下一页</span>
|
||||
<span class="i-tabler-arrow-right text-lg transition-all group-hover:translate-x-1"></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</th:block>
|
||||
</html>
|
@@ -28,7 +28,7 @@
|
||||
<th:block th:replace="~{modules/sidebar :: sidebar(prepend = ~{::sidebar_prepend})}"></th:block>
|
||||
</th:block>
|
||||
<th:block th:fragment="content">
|
||||
<div x-data="postUpvote" class="rounded-xl bg-white p-4 dark:bg-slate-800">
|
||||
<div x-data="upvote('post','content.halo.run','posts')" class="rounded-xl bg-white p-4 dark:bg-slate-800">
|
||||
<div th:attr="x-data=|{name:'${post.metadata.name}'}|" class="flex items-center justify-between">
|
||||
<div class="inline-flex items-center justify-start gap-2">
|
||||
<a th:href="@{${post.owner.permalink}}" th:title="${post.owner.displayName}">
|
||||
@@ -53,8 +53,11 @@
|
||||
<span class="text-gray-300 dark:text-slate-200">/</span>
|
||||
<span th:text="|${post.stats?.comment ?:0} 评论|"> </span>
|
||||
<span class="text-gray-300 dark:text-slate-200">/</span>
|
||||
<span th:attr="data-upvote-post-name=${post.metadata.name}" th:text="|${post.stats?.upvote ?:0} 点赞|">
|
||||
<div>
|
||||
<span th:attr="data-upvote-post-name=${post.metadata.name}" th:text="|${post.stats?.upvote ?:0}|">
|
||||
</span>
|
||||
<span> 点赞 </span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Reference in New Issue
Block a user