Compare commits

...

13 Commits

Author SHA1 Message Date
Tim
cfaa4cd094 Update application.properties 2025-09-09 11:48:42 +08:00
Tim
fc414794ff docs: use https for openapi base url 2025-09-09 11:48:07 +08:00
Tim
d8264956c3 Merge pull request #943 from nagisa77/codex/fix-invalid-workflow-permissions-in-deploy-staging.yml
fix: grant write permissions for docs deployment
2025-09-09 11:30:28 +08:00
Tim
effa7f25ca fix: grant write permissions for docs deployment 2025-09-09 11:30:11 +08:00
Tim
9b19fae69a Merge pull request #942 from nagisa77/codex/resolve-conflict-between-deploy-staging-and-deploy-docs
Run docs deployment after staging deploy
2025-09-09 11:06:39 +08:00
Tim
ec04f64ce1 chore: trigger docs deployment after staging 2025-09-09 11:06:16 +08:00
Tim
50bea76c0e Merge pull request #940 from nagisa77/codex/adjust-diff2html-font-for-mobile-ui
style: adjust diff2html fonts on mobile
2025-09-09 00:33:58 +08:00
tim
05522fcdc7 fix: 修改分割线颜色 2025-09-09 00:32:17 +08:00
tim
3820eaa774 fix: changlog--移动端支持换行 #938 2025-09-09 00:23:53 +08:00
Tim
7effaf920a style: adjust diff2html fonts on mobile 2025-09-08 23:48:32 +08:00
Tim
e40a6a3ca9 Merge pull request #935 from smallclover/main
redis功能-注册找回密码
2025-09-08 17:14:04 +08:00
Tim
7c9475cfe2 Merge pull request #936 from nagisa77/codex/fix-compilation-issues-in-postservicetest
test: add PostChangeLogService to PostService tests
2025-09-08 15:42:20 +08:00
wangshun
43fa408f46 redis功能-注册找回密码
+ 注册功能,验证码使用缓存,五分钟过期
+ 重置密码,验证码使用缓存,五分钟过期
2025-09-08 15:23:52 +08:00
11 changed files with 158 additions and 60 deletions

View File

@@ -1,7 +1,11 @@
name: Deploy Documentation name: Deploy Documentation
on: on:
push: workflow_call:
inputs:
build-id:
required: false
type: string
workflow_dispatch: workflow_dispatch:
permissions: permissions:
@@ -16,6 +20,9 @@ jobs:
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Log build
run: echo "Running documentation deployment from build ${{ inputs.build-id }}"
- name: Setup Bun - name: Setup Bun
uses: oven-sh/setup-bun@v1 uses: oven-sh/setup-bun@v1
with: with:

View File

@@ -5,6 +5,9 @@ on:
branches: [main] branches: [main]
workflow_dispatch: workflow_dispatch:
permissions:
contents: write
jobs: jobs:
build-and-deploy: build-and-deploy:
runs-on: ubuntu-latest runs-on: ubuntu-latest
@@ -21,3 +24,11 @@ jobs:
key: ${{ secrets.SSH_KEY }} key: ${{ secrets.SSH_KEY }}
script: bash /opt/openisle/deploy-staging.sh script: bash /opt/openisle/deploy-staging.sh
deploy-docs:
needs: build-and-deploy
if: ${{ success() }}
uses: ./.github/workflows/deploy-docs.yml
secrets: inherit
with:
build-id: ${{ github.run_id }}

View File

@@ -40,6 +40,8 @@ public class CachingConfig {
public static final String CATEGORY_CACHE_NAME="openisle_categories"; public static final String CATEGORY_CACHE_NAME="openisle_categories";
// 在线人数缓存名 // 在线人数缓存名
public static final String ONLINE_CACHE_NAME="openisle_online"; public static final String ONLINE_CACHE_NAME="openisle_online";
// 注册验证码
public static final String VERIFY_CACHE_NAME="openisle_verify";
/** /**
* 自定义Redis的序列化器 * 自定义Redis的序列化器

View File

@@ -1,18 +1,22 @@
package com.openisle.controller; package com.openisle.controller;
import com.openisle.config.CachingConfig;
import com.openisle.dto.*; import com.openisle.dto.*;
import com.openisle.exception.FieldException; import com.openisle.exception.FieldException;
import com.openisle.model.RegisterMode; import com.openisle.model.RegisterMode;
import com.openisle.model.User; import com.openisle.model.User;
import com.openisle.repository.UserRepository; import com.openisle.repository.UserRepository;
import com.openisle.service.*; import com.openisle.service.*;
import com.openisle.util.VerifyType;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit;
@RestController @RestController
@RequestMapping("/api/auth") @RequestMapping("/api/auth")
@@ -56,7 +60,8 @@ public class AuthController {
User user = userService.registerWithInvite( User user = userService.registerWithInvite(
req.getUsername(), req.getEmail(), req.getPassword()); req.getUsername(), req.getEmail(), req.getPassword());
inviteService.consume(req.getInviteToken(), user.getUsername()); inviteService.consume(req.getInviteToken(), user.getUsername());
emailService.sendEmail(user.getEmail(), "在网站填写验证码以验证", "您的验证码是 " + user.getVerificationCode()); // 发送确认邮件
userService.sendVerifyMail(user, VerifyType.REGISTER);
return ResponseEntity.ok(Map.of( return ResponseEntity.ok(Map.of(
"token", jwtService.generateToken(user.getUsername()), "token", jwtService.generateToken(user.getUsername()),
"reason_code", "INVITE_APPROVED" "reason_code", "INVITE_APPROVED"
@@ -70,7 +75,8 @@ public class AuthController {
} }
User user = userService.register( User user = userService.register(
req.getUsername(), req.getEmail(), req.getPassword(), "", registerModeService.getRegisterMode()); req.getUsername(), req.getEmail(), req.getPassword(), "", registerModeService.getRegisterMode());
emailService.sendEmail(user.getEmail(), "在网站填写验证码以验证", "您的验证码是 " + user.getVerificationCode()); // 发送确认邮件
userService.sendVerifyMail(user, VerifyType.REGISTER);
if (!user.isApproved()) { if (!user.isApproved()) {
notificationService.createRegisterRequestNotifications(user, user.getRegisterReason()); notificationService.createRegisterRequestNotifications(user, user.getRegisterReason());
} }
@@ -79,13 +85,12 @@ public class AuthController {
@PostMapping("/verify") @PostMapping("/verify")
public ResponseEntity<?> verify(@RequestBody VerifyRequest req) { public ResponseEntity<?> verify(@RequestBody VerifyRequest req) {
boolean ok = userService.verifyCode(req.getUsername(), req.getCode()); Optional<User> userOpt = userService.findByUsername(req.getUsername());
if (userOpt.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("error", "Invalid credentials"));
}
boolean ok = userService.verifyCode(userOpt.get(), req.getCode(), VerifyType.REGISTER);
if (ok) { if (ok) {
Optional<User> userOpt = userService.findByUsername(req.getUsername());
if (userOpt.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("error", "Invalid credentials"));
}
User user = userOpt.get(); User user = userOpt.get();
if (user.isApproved()) { if (user.isApproved()) {
@@ -122,7 +127,7 @@ public class AuthController {
User user = userOpt.get(); User user = userOpt.get();
if (!user.isVerified()) { if (!user.isVerified()) {
user = userService.register(user.getUsername(), user.getEmail(), user.getPassword(), user.getRegisterReason(), registerModeService.getRegisterMode()); user = userService.register(user.getUsername(), user.getEmail(), user.getPassword(), user.getRegisterReason(), registerModeService.getRegisterMode());
emailService.sendEmail(user.getEmail(), "在网站填写验证码以验证", "您的验证码是 " + user.getVerificationCode()); userService.sendVerifyMail(user, VerifyType.REGISTER);
return ResponseEntity.badRequest().body(Map.of( return ResponseEntity.badRequest().body(Map.of(
"error", "User not verified", "error", "User not verified",
"reason_code", "NOT_VERIFIED", "reason_code", "NOT_VERIFIED",
@@ -417,14 +422,17 @@ public class AuthController {
if (userOpt.isEmpty()) { if (userOpt.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("error", "User not found")); return ResponseEntity.badRequest().body(Map.of("error", "User not found"));
} }
String code = userService.generatePasswordResetCode(req.getEmail()); userService.sendVerifyMail(userOpt.get(), VerifyType.RESET_PASSWORD);
emailService.sendEmail(req.getEmail(), "请填写验证码以重置密码", "您的验证码是" + code);
return ResponseEntity.ok(Map.of("message", "Verification code sent")); return ResponseEntity.ok(Map.of("message", "Verification code sent"));
} }
@PostMapping("/forgot/verify") @PostMapping("/forgot/verify")
public ResponseEntity<?> verifyReset(@RequestBody VerifyForgotRequest req) { public ResponseEntity<?> verifyReset(@RequestBody VerifyForgotRequest req) {
boolean ok = userService.verifyPasswordResetCode(req.getEmail(), req.getCode()); Optional<User> userOpt = userService.findByEmail(req.getEmail());
if (userOpt.isEmpty()) {
return ResponseEntity.badRequest().body(Map.of("error", "User not found"));
}
boolean ok = userService.verifyCode(userOpt.get(), req.getCode(), VerifyType.RESET_PASSWORD);
if (ok) { if (ok) {
String username = userService.findByEmail(req.getEmail()).get().getUsername(); String username = userService.findByEmail(req.getEmail()).get().getUsername();
return ResponseEntity.ok(Map.of("token", jwtService.generateResetToken(username))); return ResponseEntity.ok(Map.of("token", jwtService.generateResetToken(username)));

View File

@@ -1,5 +1,6 @@
package com.openisle.service; package com.openisle.service;
import com.openisle.config.CachingConfig;
import com.openisle.model.User; import com.openisle.model.User;
import com.openisle.model.Role; import com.openisle.model.Role;
import com.openisle.service.PasswordValidator; import com.openisle.service.PasswordValidator;
@@ -7,13 +8,18 @@ import com.openisle.service.UsernameValidator;
import com.openisle.service.AvatarGenerator; import com.openisle.service.AvatarGenerator;
import com.openisle.exception.FieldException; import com.openisle.exception.FieldException;
import com.openisle.repository.UserRepository; import com.openisle.repository.UserRepository;
import com.openisle.util.VerifyType;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Objects;
import java.util.Optional; import java.util.Optional;
import java.util.Random; import java.util.Random;
import java.util.concurrent.TimeUnit;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -25,6 +31,10 @@ public class UserService {
private final ImageUploader imageUploader; private final ImageUploader imageUploader;
private final AvatarGenerator avatarGenerator; private final AvatarGenerator avatarGenerator;
private final RedisTemplate redisTemplate;
private final EmailSender emailService;
public User register(String username, String email, String password, String reason, com.openisle.model.RegisterMode mode) { public User register(String username, String email, String password, String reason, com.openisle.model.RegisterMode mode) {
usernameValidator.validate(username); usernameValidator.validate(username);
passwordValidator.validate(password); passwordValidator.validate(password);
@@ -38,7 +48,7 @@ public class UserService {
// 未验证 → 允许“重注册”:覆盖必要字段并重新发验证码 // 未验证 → 允许“重注册”:覆盖必要字段并重新发验证码
u.setEmail(email); // 若不允许改邮箱可去掉 u.setEmail(email); // 若不允许改邮箱可去掉
u.setPassword(passwordEncoder.encode(password)); u.setPassword(passwordEncoder.encode(password));
u.setVerificationCode(genCode()); // u.setVerificationCode(genCode());
u.setRegisterReason(reason); u.setRegisterReason(reason);
u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT); u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
return userRepository.save(u); return userRepository.save(u);
@@ -54,7 +64,7 @@ public class UserService {
// 未验证 → 允许“重注册” // 未验证 → 允许“重注册”
u.setUsername(username); // 若不允许改用户名可去掉 u.setUsername(username); // 若不允许改用户名可去掉
u.setPassword(passwordEncoder.encode(password)); u.setPassword(passwordEncoder.encode(password));
u.setVerificationCode(genCode()); // u.setVerificationCode(genCode());
u.setRegisterReason(reason); u.setRegisterReason(reason);
u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT); u.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
return userRepository.save(u); return userRepository.save(u);
@@ -67,7 +77,7 @@ public class UserService {
user.setPassword(passwordEncoder.encode(password)); user.setPassword(passwordEncoder.encode(password));
user.setRole(Role.USER); user.setRole(Role.USER);
user.setVerified(false); user.setVerified(false);
user.setVerificationCode(genCode()); // user.setVerificationCode(genCode());
user.setAvatar(avatarGenerator.generate(username)); user.setAvatar(avatarGenerator.generate(username));
user.setRegisterReason(reason); user.setRegisterReason(reason);
user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT); user.setApproved(mode == com.openisle.model.RegisterMode.DIRECT);
@@ -77,7 +87,7 @@ public class UserService {
public User registerWithInvite(String username, String email, String password) { public User registerWithInvite(String username, String email, String password) {
User user = register(username, email, password, "", com.openisle.model.RegisterMode.DIRECT); User user = register(username, email, password, "", com.openisle.model.RegisterMode.DIRECT);
user.setVerified(true); user.setVerified(true);
user.setVerificationCode(genCode()); // user.setVerificationCode(genCode());
return userRepository.save(user); return userRepository.save(user);
} }
@@ -85,16 +95,58 @@ public class UserService {
return String.format("%06d", new Random().nextInt(1000000)); return String.format("%06d", new Random().nextInt(1000000));
} }
public boolean verifyCode(String username, String code) { /**
Optional<User> userOpt = userRepository.findByUsername(username); * 将验证码存入缓存,并发送邮件
if (userOpt.isPresent() && code.equals(userOpt.get().getVerificationCode())) { * @param user
User user = userOpt.get(); */
user.setVerified(true); public void sendVerifyMail(User user, VerifyType verifyType){
user.setVerificationCode(null); //缓存验证码
userRepository.save(user); String code = genCode();
return true; String key;
String subject;
String content = "您的验证码是:" + code;
// 注册类型
if(verifyType.equals(VerifyType.REGISTER)){
key = CachingConfig.VERIFY_CACHE_NAME + ":register:code:" + user.getUsername();
subject = "在网站填写验证码以验证(有效期为5分钟)";
}else {
// 重置密码
key = CachingConfig.VERIFY_CACHE_NAME + ":reset_password:code:" + user.getUsername();
subject = "请填写验证码以重置密码(有效期为5分钟)";
} }
return false;
redisTemplate.opsForValue().set(key, code, 5, TimeUnit.MINUTES);// 五分钟后验证码过期
emailService.sendEmail(user.getEmail(), subject, content);
}
/**
* 验证code是否正确
* @param user
* @param code
* @param verifyType
* @return
*/
public boolean verifyCode(User user, String code, VerifyType verifyType) {
// 生成key
String key1 = VerifyType.REGISTER.equals(verifyType)?":register:code:":":reset_password:code:";
String key = CachingConfig.VERIFY_CACHE_NAME + key1 + user.getUsername();
// 这里不能使用getAndDelete,需要6.x版本
String cachedCode = (String)redisTemplate.opsForValue().get(key);
// 如果校验code过期或者不存在
// 或者校验code不一致
if(Objects.isNull(cachedCode)
|| !cachedCode.equals(code)){
return false;
}
// 注册模式需要设置已经确认
if(VerifyType.REGISTER.equals(verifyType)){
user.setVerified(true);
userRepository.save(user);
}
// 走到这里说明验证成功删除验证码
redisTemplate.delete(key);
return true;
} }
public Optional<User> authenticate(String username, String password) { public Optional<User> authenticate(String username, String password) {
@@ -165,26 +217,6 @@ public class UserService {
return userRepository.save(user); return userRepository.save(user);
} }
public String generatePasswordResetCode(String email) {
User user = userRepository.findByEmail(email)
.orElseThrow(() -> new com.openisle.exception.NotFoundException("User not found"));
String code = genCode();
user.setPasswordResetCode(code);
userRepository.save(user);
return code;
}
public boolean verifyPasswordResetCode(String email, String code) {
Optional<User> userOpt = userRepository.findByEmail(email);
if (userOpt.isPresent() && code.equals(userOpt.get().getPasswordResetCode())) {
User user = userOpt.get();
user.setPasswordResetCode(null);
userRepository.save(user);
return true;
}
return false;
}
public User updatePassword(String username, String newPassword) { public User updatePassword(String username, String newPassword) {
passwordValidator.validate(newPassword); passwordValidator.validate(newPassword);
User user = userRepository.findByUsername(username) User user = userRepository.findByUsername(username)

View File

@@ -0,0 +1,20 @@
package com.openisle.util;
/**
* 验证码类型
* @author smallclover
* @since 2025-09-08
*/
public enum VerifyType {
REGISTER(1),
RESET_PASSWORD(2);
private final int code;
VerifyType(int code) {
this.code = code;
}
public int getCode() {
return code;
}
}

View File

@@ -108,6 +108,7 @@ rabbitmq.sharding.enabled=true
# see https://springdoc.org/#springdoc-openapi-core-properties # see https://springdoc.org/#springdoc-openapi-core-properties
springdoc.api-docs.path=/api/v3/api-docs springdoc.api-docs.path=/api/v3/api-docs
springdoc.api-docs.enabled=true springdoc.api-docs.enabled=true
springdoc.api-docs.server-url=${WEBSITE_URL:https://www.open-isle.com}
springdoc.info.title=OpenIsle springdoc.info.title=OpenIsle
springdoc.info.description=OpenIsle Open API Documentation springdoc.info.description=OpenIsle Open API Documentation
springdoc.info.version=0.0.1 springdoc.info.version=0.0.1

View File

@@ -4,6 +4,7 @@ import com.openisle.model.User;
import com.openisle.service.*; import com.openisle.service.*;
import com.openisle.model.RegisterMode; import com.openisle.model.RegisterMode;
import com.openisle.repository.UserRepository; import com.openisle.repository.UserRepository;
import com.openisle.util.VerifyType;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.mockito.Mockito; import org.mockito.Mockito;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
@@ -71,7 +72,9 @@ class AuthControllerTest {
@Test @Test
void verifyCodeEndpoint() throws Exception { void verifyCodeEndpoint() throws Exception {
Mockito.when(userService.verifyCode("u", "123")).thenReturn(true); User user = new User();
user.setUsername("u");
Mockito.when(userService.verifyCode(user, "123", VerifyType.REGISTER)).thenReturn(true);
Mockito.when(jwtService.generateReasonToken("u")).thenReturn("reason_token"); Mockito.when(jwtService.generateReasonToken("u")).thenReturn("reason_token");
mockMvc.perform(post("/api/auth/verify") mockMvc.perform(post("/api/auth/verify")

View File

@@ -17,7 +17,7 @@
--background-color: white; --background-color: white;
--background-color-blur: rgba(255, 255, 255, 0.57); --background-color-blur: rgba(255, 255, 255, 0.57);
--menu-border-color: lightgray; --menu-border-color: lightgray;
--normal-border-color: lightgray; --normal-border-color: rgba(211, 211, 211, 0.63);
--menu-selected-background-color: rgba(88, 241, 255, 0.166); --menu-selected-background-color: rgba(88, 241, 255, 0.166);
--normal-light-background-color: rgba(242, 242, 242, 0.884); --normal-light-background-color: rgba(242, 242, 242, 0.884);
--menu-selected-background-color-hover: rgba(242, 242, 242, 0.884); --menu-selected-background-color-hover: rgba(242, 242, 242, 0.884);
@@ -348,6 +348,22 @@ body {
} }
} }
/* Adjust diff2html layout on mobile */
@media (max-width: 768px) {
.content-diff .d2h-wrapper,
.content-diff .d2h-code-line,
.content-diff .d2h-code-side-line,
.content-diff .d2h-code-line-ctn,
.content-diff .d2h-code-side-line-ctn,
.content-diff .d2h-file-header {
font-size: 12px;
}
.content-diff .d2h-wrapper {
overflow-x: auto;
}
}
/* Transition API */ /* Transition API */
::view-transition-old(root), ::view-transition-old(root),
::view-transition-new(root) { ::view-transition-new(root) {

View File

@@ -11,18 +11,18 @@
<span v-if="log.username" class="change-log-user">{{ log.username }}</span> <span v-if="log.username" class="change-log-user">{{ log.username }}</span>
<span v-if="log.type === 'CONTENT'" class="change-log-content">变更了文章内容</span> <span v-if="log.type === 'CONTENT'" class="change-log-content">变更了文章内容</span>
<span v-else-if="log.type === 'TITLE'" class="change-log-content">变更了文章标题</span> <span v-else-if="log.type === 'TITLE'" class="change-log-content">变更了文章标题</span>
<span v-else-if="log.type === 'CATEGORY'" class="change-log-content change-log-category"> <template v-else-if="log.type === 'CATEGORY'">
<div class="change-log-category-text">变更了文章分类, </div> <div class="change-log-category-text">变更了文章分类, </div>
<ArticleCategory :category="log.oldCategory" /> <ArticleCategory :category="log.oldCategory" />
<div class="change-log-category-text">修改为</div> <div class="change-log-category-text">修改为</div>
<ArticleCategory :category="log.newCategory" /> <ArticleCategory :category="log.newCategory" />
</span> </template>
<span v-else-if="log.type === 'TAG'" class="change-log-content change-log-category"> <template v-else-if="log.type === 'TAG'">
<div class="change-log-category-text">变更了文章标签, </div> <div class="change-log-category-text">变更了文章标签, </div>
<ArticleTags :tags="log.oldTags" /> <ArticleTags :tags="log.oldTags" />
<div class="change-log-category-text">修改为</div> <div class="change-log-category-text">修改为</div>
<ArticleTags :tags="log.newTags" /> <ArticleTags :tags="log.newTags" />
</span> </template>
<span v-else-if="log.type === 'CLOSED'" class="change-log-content"> <span v-else-if="log.type === 'CLOSED'" class="change-log-content">
<template v-if="log.newClosed">关闭了文章</template> <template v-if="log.newClosed">关闭了文章</template>
<template v-else>重新打开了文章</template> <template v-else>重新打开了文章</template>
@@ -68,7 +68,6 @@ const props = defineProps({
}) })
const diffHtml = computed(() => { const diffHtml = computed(() => {
const isMobile = useIsMobile()
// Track theme changes // Track theme changes
const isDark = import.meta.client && document.documentElement.dataset.theme === 'dark' const isDark = import.meta.client && document.documentElement.dataset.theme === 'dark'
themeState.mode themeState.mode
@@ -83,7 +82,6 @@ const diffHtml = computed(() => {
showFiles: false, showFiles: false,
matching: 'lines', matching: 'lines',
drawFileList: false, drawFileList: false,
outputFormat: isMobile.value ? 'line-by-line' : 'side-by-side',
colorScheme, colorScheme,
}) })
} else if (props.log.type === 'TITLE') { } else if (props.log.type === 'TITLE') {
@@ -95,7 +93,6 @@ const diffHtml = computed(() => {
showFiles: false, showFiles: false,
matching: 'lines', matching: 'lines',
drawFileList: false, drawFileList: false,
outputFormat: isMobile.value ? 'line-by-line' : 'side-by-side',
colorScheme, colorScheme,
}) })
} }
@@ -110,9 +107,12 @@ const diffHtml = computed(() => {
/* padding-top: 5px; */ /* padding-top: 5px; */
/* padding-bottom: 30px; */ /* padding-bottom: 30px; */
font-size: 14px; font-size: 14px;
border-bottom: 1px solid var(--normal-border-color);
padding-bottom: 10px;
} }
.change-log-text { .change-log-text {
display: flex; display: flex;
flex-wrap: wrap;
align-items: center; align-items: center;
} }
.change-log-user { .change-log-user {
@@ -146,5 +146,6 @@ const diffHtml = computed(() => {
flex-direction: row; flex-direction: row;
gap: 4px; gap: 4px;
align-items: center; align-items: center;
flex-wrap: wrap;
} }
</style> </style>

View File

@@ -1163,6 +1163,7 @@ onMounted(async () => {
margin-top: 10px; margin-top: 10px;
gap: 10px; gap: 10px;
align-items: center; align-items: center;
flex-wrap: wrap;
} }
.info-content-container { .info-content-container {
@@ -1218,7 +1219,7 @@ onMounted(async () => {
} }
.post-time { .post-time {
font-size: 14px; font-size: 12px;
opacity: 0.5; opacity: 0.5;
} }
@@ -1284,10 +1285,6 @@ onMounted(async () => {
font-size: 12px; font-size: 12px;
} }
.post-time {
font-size: 12px;
}
.info-content-text { .info-content-text {
line-height: 1.5; line-height: 1.5;
} }