diff --git a/blossom-backend/backend/pom.xml b/blossom-backend/backend/pom.xml index 18562d7..0a3b64a 100644 --- a/blossom-backend/backend/pom.xml +++ b/blossom-backend/backend/pom.xml @@ -5,7 +5,7 @@ blossom-backend com.blossom - 1.15.0-SNAPSHOT + 1.16.0-SNAPSHOT 4.0.0 diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/backup/ArticleBackupService.java b/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/backup/ArticleBackupService.java index 7589557..cf6c7da 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/backup/ArticleBackupService.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/backup/ArticleBackupService.java @@ -9,6 +9,8 @@ import cn.hutool.core.util.ZipUtil; import com.blossom.backend.base.param.ParamEnum; import com.blossom.backend.base.param.ParamService; import com.blossom.backend.base.param.pojo.ParamEntity; +import com.blossom.backend.base.paramu.UserParamEnum; +import com.blossom.backend.base.paramu.UserParamService; import com.blossom.backend.base.user.UserService; import com.blossom.backend.base.user.pojo.UserEntity; import com.blossom.backend.server.article.backup.pojo.BackupFile; @@ -56,6 +58,9 @@ public class ArticleBackupService { @Autowired private ParamService paramService; + @Autowired + private UserParamService userParamService; + @Autowired private UserService userService; @@ -126,6 +131,13 @@ public class ArticleBackupService { // 用户信息 UserEntity user = userService.selectById(userId); + final String BLOG_COLOR = userParamService.getValue(userId, UserParamEnum.WEB_BLOG_COLOR).getParamValue(); + final String WATER_ENABLED = userParamService.getValue(userId, UserParamEnum.WEB_BLOG_WATERMARK_ENABLED).getParamValue(); + final String WATERMARK_CONTENT = userParamService.getValue(userId, UserParamEnum.WEB_BLOG_WATERMARK_CONTENT).getParamValue(); + final String WATERMARK_FONTSIZE = userParamService.getValue(userId, UserParamEnum.WEB_BLOG_WATERMARK_FONTSIZE).getParamValue(); + final String WATERMARK_COLOR = userParamService.getValue(userId, UserParamEnum.WEB_BLOG_WATERMARK_COLOR).getParamValue(); + final String WATERMARK_GAP = userParamService.getValue(userId, UserParamEnum.WEB_BLOG_WATERMARK_GAP).getParamValue(); + final File backLogFile = new File(backupFile.getRootPath() + "/" + "log.txt"); final List backLogs = new ArrayList<>(); log.info("[文章备份] 开始备份, 本次备份文件名称 [{}], 用户ID [{}]", backupFile.getFilename(), userId); @@ -186,7 +198,9 @@ public class ArticleBackupService { articleDetail.setName(article.getN()); // 文章 markdown 内容 - String content = getContentByType(articleDetail, type, user); + String content = getContentByType(articleDetail, type, user, + BLOG_COLOR, WATER_ENABLED, WATERMARK_CONTENT, WATERMARK_FONTSIZE, WATERMARK_COLOR, WATERMARK_GAP + ); content = formatContent(content, toLocal, article.getI(), article.getN()); String id = String.valueOf(articleDetail.getId()); String version = String.valueOf(articleDetail.getVersion()); @@ -231,17 +245,19 @@ public class ArticleBackupService { } // 备份全部图片 else { - List files = FileUtil.loopFiles(FileUtil.newFile(iaasProperties.getBlos().getDefaultPath() + "/U" + userId), null); - backLogs.add("[图片备份] 图片个数: " + files.size()); + List pics = FileUtil.loopFiles(FileUtil.newFile(iaasProperties.getBlos().getDefaultPath() + "/U" + userId), null); + backLogs.add("[图片备份] 图片个数: " + pics.size()); backLogs.add("┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ↓↓ 图片列表 ↓↓ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); - for (File file : files) { + for (File file : pics) { backLogs.add(file.getPath()); } - FileUtil.copy( - iaasProperties.getBlos().getDefaultPath() + "/U" + userId, - backupFile.getRootPath() + "/" + iaasProperties.getBlos().getDefaultPath(), - true); + if (CollUtil.isNotEmpty(pics)) { + FileUtil.copy( + iaasProperties.getBlos().getDefaultPath() + "/U" + userId, + backupFile.getRootPath() + "/" + iaasProperties.getBlos().getDefaultPath(), + true); + } } backLogs.add("┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ↑↑ 图片列表 ↑↑ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"); @@ -353,18 +369,31 @@ public class ArticleBackupService { /** * 根据类型判断是获取 markdown 内容还是 html 内容 * - * @param article 文章 - * @param type 类型 - * @return 对应的内容 + * @param article 文章 + * @param type 类型 + * @param user 用户信息 + * @param blogColor 主题色 + * @param watermarkEnabled 开启水印 + * @param watermarkContent 水印内容 + * @param waterFontSize 水印字体大小 + * @param waterColor 水印颜色 + * @param waterGap 水印密集度 + * @return */ - private String getContentByType(ArticleEntity article, BackupTypeEnum type, UserEntity user) { + private String getContentByType(ArticleEntity article, BackupTypeEnum type, UserEntity user, + String blogColor, + String watermarkEnabled, + String watermarkContent, + String waterFontSize, + String waterColor, + String waterGap) { if (type == BackupTypeEnum.MARKDOWN) { return StrUtil.isBlank(article.getMarkdown()) ? "文章无内容" : article.getMarkdown(); } else if (type == BackupTypeEnum.HTML) { ArticleEntity export = new ArticleEntity(); export.setName(article.getName().substring(article.getName().lastIndexOf("/"))); export.setHtml(article.getHtml()); - return ArticleUtil.toHtml(article, user); + return ArticleUtil.toHtml(article, user, blogColor, watermarkEnabled, watermarkContent, waterFontSize, waterColor, waterGap); } return ""; } diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/draft/ArticleController.java b/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/draft/ArticleController.java index 4eeb1cd..21c3d8d 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/draft/ArticleController.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/server/article/draft/ArticleController.java @@ -7,6 +7,8 @@ import cn.hutool.core.util.ObjUtil; import cn.hutool.core.util.StrUtil; import com.blossom.backend.base.auth.AuthContext; import com.blossom.backend.base.auth.annotation.AuthIgnore; +import com.blossom.backend.base.paramu.UserParamEnum; +import com.blossom.backend.base.paramu.UserParamService; import com.blossom.backend.base.user.UserService; import com.blossom.backend.server.article.draft.pojo.*; import com.blossom.backend.server.article.open.ArticleOpenService; @@ -60,6 +62,7 @@ public class ArticleController { private final DocService docService; private final DocSortChecker docSortChecker; private final ImportManager importManager; + private final UserParamService userParamService; /** * 查询列表 @@ -260,7 +263,15 @@ public class ArticleController { if (StrUtil.isBlank(article.getHtml())) { article.setHtml("文章无内容"); } - String reportHtml = ArticleUtil.toHtml(article, userService.selectById(AuthContext.getUserId())); + String reportHtml = ArticleUtil.toHtml(article, + userService.selectById(AuthContext.getUserId()), + userParamService.getValue(AuthContext.getUserId(), UserParamEnum.WEB_BLOG_COLOR).getParamValue(), + userParamService.getValue(AuthContext.getUserId(), UserParamEnum.WEB_BLOG_WATERMARK_ENABLED).getParamValue(), + userParamService.getValue(AuthContext.getUserId(), UserParamEnum.WEB_BLOG_WATERMARK_CONTENT).getParamValue(), + userParamService.getValue(AuthContext.getUserId(), UserParamEnum.WEB_BLOG_WATERMARK_FONTSIZE).getParamValue(), + userParamService.getValue(AuthContext.getUserId(), UserParamEnum.WEB_BLOG_WATERMARK_COLOR).getParamValue(), + userParamService.getValue(AuthContext.getUserId(), UserParamEnum.WEB_BLOG_WATERMARK_GAP).getParamValue() + ); try (InputStream is = new ByteArrayInputStream(reportHtml.getBytes(StandardCharsets.UTF_8)); BufferedInputStream bis = new BufferedInputStream(is)) { String filename = URLEncodeUtil.encode(article.getName() + ".html"); @@ -331,6 +342,15 @@ public class ArticleController { XzException404.throwBy(ObjUtil.isNull(visit), "文章不存在或您无权限查看"); ArticleEntity article = baseService.selectById(visit.getArticleId(), false, false, true, visit.getUserId()); resp.setContentType("text/html"); - return ArticleUtil.toHtml(article, userService.selectById(visit.getUserId())); + return ArticleUtil.toHtml( + article, + userService.selectById(visit.getUserId()), + userParamService.getValue(visit.getUserId(), UserParamEnum.WEB_BLOG_COLOR).getParamValue(), + userParamService.getValue(visit.getUserId(), UserParamEnum.WEB_BLOG_WATERMARK_ENABLED).getParamValue(), + userParamService.getValue(visit.getUserId(), UserParamEnum.WEB_BLOG_WATERMARK_CONTENT).getParamValue(), + userParamService.getValue(visit.getUserId(), UserParamEnum.WEB_BLOG_WATERMARK_FONTSIZE).getParamValue(), + userParamService.getValue(visit.getUserId(), UserParamEnum.WEB_BLOG_WATERMARK_COLOR).getParamValue(), + userParamService.getValue(visit.getUserId(), UserParamEnum.WEB_BLOG_WATERMARK_GAP).getParamValue() + ); } } diff --git a/blossom-backend/backend/src/main/java/com/blossom/backend/server/utils/ArticleUtil.java b/blossom-backend/backend/src/main/java/com/blossom/backend/server/utils/ArticleUtil.java index 82e07c6..6563566 100644 --- a/blossom-backend/backend/src/main/java/com/blossom/backend/server/utils/ArticleUtil.java +++ b/blossom-backend/backend/src/main/java/com/blossom/backend/server/utils/ArticleUtil.java @@ -1,18 +1,16 @@ package com.blossom.backend.server.utils; import cn.hutool.core.util.ArrayUtil; -import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; import com.blossom.backend.base.user.pojo.UserEntity; import com.blossom.backend.server.article.draft.pojo.ArticleEntity; -import com.blossom.common.base.util.DateUtils; +import com.blossom.common.base.enums.YesNo; import lombok.extern.slf4j.Slf4j; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import java.io.IOException; import java.io.InputStream; -import java.util.Date; /** * 文章工具类 @@ -102,32 +100,265 @@ public class ArticleUtil { } + /** + * + */ + private static final String HEAD_SCRIPT_BLOG_COLOR = " \n"; - private static final String prefix = "\n" + - "
\n" + - "
本文作者:{BLOSSOM_EXPORT_HTML_AUTHOR}。著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
{\n" + + " ctx.fillText(\n" + + " item ?? 'ccccc',\n" + + " contentWidth / 2,\n" + + " index * (mergedFontSize + FontGap * ratio)\n" + + " )\n" + + " })\n" + + "\n" + + " // ==================== Rotate ====================\n" + + " const angle = (Math.PI / 180) * Number(rotate)\n" + + " const maxSize = Math.max(width, height)\n" + + " const [rCtx, rCanvas, realMaxSize] = prepareCanvas(maxSize, maxSize, ratio)\n" + + "\n" + + " // Copy from `ctx` and rotate\n" + + " rCtx.translate(realMaxSize / 2, realMaxSize / 2)\n" + + " rCtx.rotate(angle)\n" + + " if (contentWidth > 0 && contentHeight > 0) {\n" + + " rCtx.drawImage(canvas, -contentWidth / 2, -contentHeight / 2)\n" + + " }\n" + + "\n" + + " // Get boundary of rotated text\n" + + " function getRotatePos(x, y) {\n" + + " const targetX = x * Math.cos(angle) - y * Math.sin(angle)\n" + + " const targetY = x * Math.sin(angle) + y * Math.cos(angle)\n" + + " return [targetX, targetY]\n" + + " }\n" + + "\n" + + " let left = 0\n" + + " let right = 0\n" + + " let top = 0\n" + + " let bottom = 0\n" + + "\n" + + " const halfWidth = contentWidth / 2\n" + + " const halfHeight = contentHeight / 2\n" + + " const points = [\n" + + " [0 - halfWidth, 0 - halfHeight],\n" + + " [0 + halfWidth, 0 - halfHeight],\n" + + " [0 + halfWidth, 0 + halfHeight],\n" + + " [0 - halfWidth, 0 + halfHeight],\n" + + " ]\n" + + " points.forEach(([x, y]) => {\n" + + " const [targetX, targetY] = getRotatePos(x, y)\n" + + " left = Math.min(left, targetX)\n" + + " right = Math.max(right, targetX)\n" + + " top = Math.min(top, targetY)\n" + + " bottom = Math.max(bottom, targetY)\n" + + " })\n" + + "\n" + + " const cutLeft = left + realMaxSize / 2\n" + + " const cutTop = top + realMaxSize / 2\n" + + " const cutWidth = right - left\n" + + " const cutHeight = bottom - top\n" + + "\n" + + " // ================ Fill Alternate ================\n" + + " const realGapX = gapX * ratio\n" + + " const realGapY = gapY * ratio\n" + + " const filledWidth = (cutWidth + realGapX) * 2\n" + + " const filledHeight = cutHeight + realGapY\n" + + "\n" + + " const [fCtx, fCanvas] = prepareCanvas(filledWidth, filledHeight)\n" + + "\n" + + " function drawImg(targetX = 0, targetY = 0) {\n" + + " fCtx.drawImage(rCanvas, cutLeft, cutTop, cutWidth, cutHeight, targetX, targetY, cutWidth, cutHeight)\n" + + " }\n" + + " drawImg()\n" + + " drawImg(cutWidth + realGapX, -cutHeight / 2 - realGapY / 2)\n" + + " drawImg(cutWidth + realGapX, +cutHeight / 2 + realGapY / 2)\n" + + " return [fCanvas.toDataURL(), filledWidth / ratio, filledHeight / ratio]\n" + + " }\n" + + " \n" + + " const getMarkSize = (ctx, content) => {\n" + + " let defaultWidth = 120\n" + + " let defaultHeight = 64\n" + + " const width = 120\n" + + " const height = 64\n" + + " if (ctx.measureText) {\n" + + " ctx.font = `${Number('15')}px sans-serif`\n" + + " const contents = Array.isArray(content) ? content : [content]\n" + + " const sizes = contents.map((item) => {\n" + + " const metrics = ctx.measureText(item)\n" + + "\n" + + " return [metrics.width,\n" + + " metrics.fontBoundingBoxAscent !== undefined\n" + + " ? metrics.fontBoundingBoxAscent + metrics.fontBoundingBoxDescent\n" + + " : metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent,\n" + + " ]\n" + + " })\n" + + " defaultWidth = Math.ceil(Math.max(...sizes.map((size) => size[0])))\n" + + " defaultHeight =\n" + + " Math.ceil(Math.max(...sizes.map((size) => size[1]))) * contents.length +\n" + + " (contents.length - 1) * FontGap\n" + + " }\n" + + " return [width ?? defaultWidth, height ?? defaultHeight]\n" + + " }\n" + + " \n" + + " function toLowercaseSeparator(key) {\n" + + " return key.replace(/([A-Z])/g, '-$1').toLowerCase()\n" + + " }\n" + + "\n" + + " function getStyleStr(style) {\n" + + " return Object.keys(style)\n" + + " .map(\n" + + " (key) =>\n" + + " `${toLowercaseSeparator(key)}: ${style[key]};`\n" + + " )\n" + + " .join(' ')\n" + + " }\n" + + "\n" + + " const getMarkStyle = (rotate) => {\n" + + " const markStyle = {zIndex: 9, position: 'absolute', left: 0, top: 0, width: '100%', height: '100%', pointerEvents: 'none', backgroundRepeat: 'repeat' }\n" + + "\n" + + " /** Calculate the style of the offset */\n" + + " let positionLeft = rotate / 2 - rotate / 2\n" + + " let positionTop = rotate / 2 - rotate / 2\n" + + " if (positionLeft > 0) {\n" + + " markStyle.left = `${positionLeft}px`\n" + + " markStyle.width = `calc(100% - ${positionLeft}px)`\n" + + " positionLeft = 0\n" + + " }\n" + + " if (positionTop > 0) {\n" + + " markStyle.top = `${positionTop}px`\n" + + " markStyle.height = `calc(100% - ${positionTop}px)`\n" + + " positionTop = 0\n" + + " }\n" + + " markStyle.backgroundPosition = `${positionLeft}px ${positionTop}px`\n" + + " return markStyle\n" + + " }\n" + + " \n" + + " let stopObservation = false\n" + + " const observerContainer = document.getElementsByClassName('bl-preview')[0]\n" + + " const observerConfig = { attributes: true, childList: true, subtree: true };\n" + + " const observer = new MutationObserver(() => {\n" + + " renderWatermark()\n" + + " });\n" + + "\n" + + " const renderWatermark = () => {\n" + + " const canvas = document.createElement(\"canvas\")\n" + + " const ctx = canvas.getContext(\"2d\")\n" + + " const rotate = -22\n" + + " const content = '{WEB_BLOG_WATERMARK_CONTENT}'\n" + + " const color = '{WEB_BLOG_WATERMARK_COLOR}'\n" + + " const fontSize = {WEB_BLOG_WATERMARK_FONTSIZE}\n" + + " const gap = {WEB_BLOG_WATERMARK_GAP}\n" + + "\n" + + " if (ctx) {\n" + + " const ratio = window.devicePixelRatio || 1\n" + + " const [markWidth, markHeight] = getMarkSize(ctx, content)\n" + + "\n" + + " const drawCanvas = (drawContent) => {\n" + + " const [textClips, clipWidth] = getClips(drawContent, rotate, ratio, markWidth, markHeight, \n" + + " {color: color, fontSize: fontSize, fontStyle: 'normal', fontWeight: 'normal'}, gap, gap\n" + + " )\n" + + " let watermark = document.createElement('div')\n" + + " let container = document.getElementsByClassName('bl-preview')[0]\n" + + " stopObservation = true\n" + + " observer.disconnect()\n" + + " watermark.setAttribute('style',getStyleStr({\n" + + " ...getMarkStyle(rotate),\n" + + " backgroundImage: `url('${textClips}')`,\n" + + " backgroundSize: `${Math.floor(clipWidth)}px`,\n" + + " })\n" + + " )\n" + + " container.append(watermark)\n" + + " setTimeout(() => {\n" + + " observer.observe(observerContainer, observerConfig)\n" + + " })\n" + + " };\n" + + " \n" + + " drawCanvas(content);\n" + + " }\n" + + " };\n" + + "\n" + + " renderWatermark()\n" + + " })\n" + + " \n"; + + + private static final String BODY_HEADER = "\n" + + "\n" + + "
\n" + + "
本文作者:{BLOSSOM_EXPORT_HTML_AUTHOR}。著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
Export by Blossom\n" + - " \n" + - "\n" + - "
\n" + + " \n" + + "
\n" + + "
\n" + + "
\n" + + "
"; + + private static final String BODY_CONTENT_TOC = "\n" + "
\n" + "
《{BLOSSOM_EXPORT_HTML_ARTICLE_NAME}》
\n" + - "
目录
\n" + - "
"; + "
目录
\n" + + "
\n"; - private static final String suffix = "
"; + private static final String suffix = "
"; - private static String htmlTag; + private static String htmlTemplate; static { Resource resource = new ClassPathResource("exportTemplate.html"); try (InputStream is = resource.getInputStream()) { byte[] bytes = new byte[is.available()]; is.read(bytes); - htmlTag = new String(bytes); + htmlTemplate = new String(bytes); } catch (IOException e) { e.printStackTrace(); } @@ -136,44 +367,90 @@ public class ArticleUtil { /** * 将文章转换为 html 格式 * - * @param article 文章 - * @param user 用户, 用户获取作者 + * @param article 文章 + * @param user 用户, 用户获取作者 + * @param blogColor 主颜色 + * @param WEB_BLOG_WATERMARK_ENABLED 开启水印 + * @param WEB_BLOG_WATERMARK_CONTENT 水印内容 + * @param WEB_BLOG_WATERMARK_FONTSIZE 水印字体大小 + * @param WEB_BLOG_WATERMARK_COLOR 水印颜色 + * @param WEB_BLOG_WATERMARK_GAP 水印密集度 * @return html 内容 */ - public static String toHtml(ArticleEntity article, UserEntity user) { - return htmlTag + prefix - // 替换作者, 文章名称 - .replaceAll("\\{BLOSSOM_EXPORT_HTML_AUTHOR}", user.getNickName()) - .replaceAll("\\{BLOSSOM_EXPORT_HTML_ARTICLE_NAME}", article.getName()) + - article.getHtml() + suffix; + public static String toHtml(ArticleEntity article, + UserEntity user, + String blogColor, + String WEB_BLOG_WATERMARK_ENABLED, + String WEB_BLOG_WATERMARK_CONTENT, + String WEB_BLOG_WATERMARK_FONTSIZE, + String WEB_BLOG_WATERMARK_COLOR, + String WEB_BLOG_WATERMARK_GAP) { + return htmlTemplate + + appendHeadScript(blogColor, WEB_BLOG_WATERMARK_ENABLED, WEB_BLOG_WATERMARK_CONTENT, WEB_BLOG_WATERMARK_FONTSIZE, WEB_BLOG_WATERMARK_COLOR, WEB_BLOG_WATERMARK_GAP) + + appendBodyHeader(user) + + appendArticleContent(article) + + appendToc(article) + + suffix; } - - private static void genHeatmap() { - Date begin = DateUtils.parse("2023-04-01", DateUtils.PATTERN_YYYYMMDD); - Date end = DateUtils.parse("2023-06-30", DateUtils.PATTERN_YYYYMMDD); - for (; DateUtils.compare(begin, end) <= 0; begin = DateUtils.offsetDay(begin, 1)) { - String dt = DateUtils.format(begin, DateUtils.PATTERN_YYYYMMDD); - int value = RandomUtil.randomInt(0, 10); - System.out.println(String.format("insert into blossom_stat values(null, 1, '%s' ,%s);", dt, value)); + /** + * 添加 head 下的动态 script + * + * @param blogColor 主题色 + * @param WEB_BLOG_WATERMARK_ENABLED 开启水印 + * @param WEB_BLOG_WATERMARK_CONTENT 水印内容 + * @param WEB_BLOG_WATERMARK_FONTSIZE 水印字体大小 + * @param WEB_BLOG_WATERMARK_COLOR 水印颜色 + * @param WEB_BLOG_WATERMARK_GAP 水印密集度 + * @return head 下的所有动态 script + */ + private static String appendHeadScript(String blogColor, + String WEB_BLOG_WATERMARK_ENABLED, + String WEB_BLOG_WATERMARK_CONTENT, + String WEB_BLOG_WATERMARK_FONTSIZE, + String WEB_BLOG_WATERMARK_COLOR, + String WEB_BLOG_WATERMARK_GAP) { + String script = HEAD_SCRIPT_BLOG_COLOR.replaceAll("\\{BLOSSOM_WEB_BLOG_COLOR}", blogColor); + if (YesNo.YES.getValue().toString().equals(WEB_BLOG_WATERMARK_ENABLED)) { + script += HEAD_SCRIPT_WATERMARK + .replaceAll("\\{WEB_BLOG_WATERMARK_CONTENT}", WEB_BLOG_WATERMARK_CONTENT) + .replaceAll("\\{WEB_BLOG_WATERMARK_FONTSIZE}", WEB_BLOG_WATERMARK_FONTSIZE) + .replaceAll("\\{WEB_BLOG_WATERMARK_COLOR}", WEB_BLOG_WATERMARK_COLOR) + .replaceAll("\\{WEB_BLOG_WATERMARK_GAP}", WEB_BLOG_WATERMARK_GAP); } + return script + ""; } - private static void genWords() { - int value = 203012; - Date begin = DateUtils.parse("2019-01-01", DateUtils.PATTERN_YYYYMMDD); - Date end = DateUtils.parse("2023-06-30", DateUtils.PATTERN_YYYYMMDD); - for (; DateUtils.compare(begin, end) <= 0; begin = DateUtils.offsetMonth(begin, 1)) { - String dt = DateUtils.format(begin, DateUtils.PATTERN_YYYYMMDD); - value = value + RandomUtil.randomInt(0, 10000); - System.out.println(String.format("insert into blossom_stat values(null, 2, '%s' ,%s);", dt, value)); - } + /** + * 添加页面顶部的文章作者和目录顶部的文章名称 + * + * @param article 文章 + * @param user 用户, 用户获取作者 + * @return 页面顶部的文章作者和文章名称 + */ + private static String appendBodyHeader(UserEntity user) { + return BODY_HEADER + .replaceAll("\\{BLOSSOM_EXPORT_HTML_AUTHOR}", user.getNickName()); } - public static void main(String[] args) { -// ArticleEntity a = new ArticleEntity(); -// a.setName("123123213"); -// a.setHtml("asdasd"); -// System.out.println(exportHtml(a)); + /** + * 添加正文 + * + * @param article 文章 + */ + private static String appendArticleContent(ArticleEntity article) { + return article.getHtml() + + "\n
" + + "\n "; + } + + /** + * 添加目录 + * + * @param article 文章 + */ + private static String appendToc(ArticleEntity article) { + return BODY_CONTENT_TOC + .replaceAll("\\{BLOSSOM_EXPORT_HTML_ARTICLE_NAME}", article.getName()); } } diff --git a/blossom-backend/backend/src/main/resources/exportTemplate.html b/blossom-backend/backend/src/main/resources/exportTemplate.html index de2049c..e4f47ea 100644 --- a/blossom-backend/backend/src/main/resources/exportTemplate.html +++ b/blossom-backend/backend/src/main/resources/exportTemplate.html @@ -1358,6 +1358,9 @@ color: #c5c5c5; border-bottom: 1px solid #eaeaea; text-align: right; + position: fixed; + z-index: 999; + background-color: #fff; } .header .copyright { @@ -1380,7 +1383,7 @@ display: flex; flex-direction: row; justify-content: flex-start; - align-items: flex-start + align-items: flex-start; } .content .main { @@ -1388,16 +1391,19 @@ width: calc(100% - 300px); padding: 20px; overflow-x: hidden; - overflow-y: scroll + overflow-y: scroll; + margin-top: 30px; } .toc { height: 100%; width: 300px; - padding-left: 15px; - border-right: 1px solid #eeeeee; + padding-left: 10px; + padding-bottom: 10px; + border-left: 1px solid #eeeeee; overflow-x: hidden; - overflow-y: scroll + overflow-y: scroll; + margin-top: 30px; } .toc > h1, @@ -1406,11 +1412,32 @@ .toc > h4, .toc > h5, .toc > h6 { - margin: 0; + margin-top: 0; + margin-bottom: 0; + line-height: 27px; font-size: 15px; font-weight: 300; cursor: pointer; - color: #727272 + color: #727272; + position: relative; + } + + .toc > h1::after, + .toc > h2::after, + .toc > h3::after, + .toc > h4::after, + .toc > h5::after, + .toc > h6::after { + content: ""; + position: absolute; + top: 20%; + left: -5px; + width: 2px; + height: 60%; + background: var(--bl-color-primary); + border-radius: 10px; + opacity: 0; + transition: opacity 0.1s; } .toc > h1:hover, @@ -1419,37 +1446,42 @@ .toc > h4:hover, .toc > h5:hover, .toc > h6:hover { - color: #3b3b3b + color: var(--bl-color-primary); } - .toc-h1 { - margin-top: 5px !important; - padding-top: 5px + .toc > h1:hover::after, + .toc > h2:hover::after, + .toc > h3:hover::after, + .toc > h4:hover::after, + .toc > h5:hover::after, + .toc > h6:hover::after { + opacity: 1; } .toc-h2 { - padding-left: 10px + margin-left: 10px; } .toc-h3 { - padding-left: 20px + margin-left: 20px; } .toc-h4 { - padding-left: 30px + margin-left: 30px; } .toc-h5 { - padding-left: 40px + margin-left: 40px; } .toc-h6 { - padding-left: 50px + margin-left: 50px; }