diff --git a/.eslintrc.js b/.eslintrc.js index 4102e754..926a4bb4 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -64,6 +64,7 @@ module.exports = { 'import/no-extraneous-dependencies': 0, 'global-require': 0, 'no-continue': 0, + 'linebreak-style': 0, }, parserOptions: { parser: '@typescript-eslint/parser', diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000..0453efcd --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +registry=https://registry.npmjs.org \ No newline at end of file diff --git a/package.json b/package.json index 1dedc72a..f36fbc8b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gridea", - "version": "0.9.2", + "version": "0.9.3", "private": true, "description": "A static blog writing client. You can use it to record your life, mood, knowledge, notes and ideas...", "keywords": [ @@ -8,7 +8,7 @@ "static-site", "static-site-generator" ], - "homepage": "http://gridea.dev", + "homepage": "https://gridea.dev", "license": "MIT", "author": { "name": "EryouHao", @@ -25,7 +25,7 @@ "@iktakahiro/markdown-it-katex": "^3.1.0", "@sentry/electron": "^1.2.0", "ant-design-vue": "^1.3.5", - "axios": "^0.18.1", + "axios": "^0.27.2", "bluebird": "^3.5.3", "ejs": "^2.6.1", "electron-google-analytics": "^0.1.0", @@ -34,7 +34,7 @@ "fs-extra": "^7.0.1", "gray-matter": "^4.0.1", "hpagent": "^1.0.0", - "isomorphic-git": "^1.17.1", + "isomorphic-git": "1.17.1", "junk": "^3.1.0", "less": "^3.9.0", "lowdb": "^1.0.0", diff --git a/public/default-files/posts/hello-gridea.md b/public/default-files/posts/hello-gridea.md index 29786132..d0990c90 100644 --- a/public/default-files/posts/hello-gridea.md +++ b/public/default-files/posts/hello-gridea.md @@ -13,7 +13,7 @@ feature: /post-images/hello-gridea.png [Github](https://github.com/getgridea/gridea) [Gridea 主页](https://gridea.dev/) -[示例网站](http://fehey.com/) +[示例网站](https://fehey.com/) ## 特性👇 📝 你可以使用最酷的 **Markdown** 语法,进行快速创作 diff --git a/public/default-files/themes/fly/templates/includes/head.ejs b/public/default-files/themes/fly/templates/includes/head.ejs index 97dd2f8e..ce876fab 100644 --- a/public/default-files/themes/fly/templates/includes/head.ejs +++ b/public/default-files/themes/fly/templates/includes/head.ejs @@ -1,37 +1,37 @@ - - - -<%= siteTitle %> - - - - - - - - - - -<% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %> - <% if (commentSetting.commentPlatform === 'gitalk') { %> - - <% } %> - - <% if (commentSetting.commentPlatform === 'disqus') { %> - - <% } %> -<% } %> - - - - -<% if (site.customConfig.ga) { %> - - -<% } %> + + + +<%= siteTitle %> + + + + + + + + + + +<% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %> + <% if (commentSetting.commentPlatform === 'gitalk') { %> + + <% } %> + + <% if (commentSetting.commentPlatform === 'disqus') { %> + + <% } %> +<% } %> + + + + +<% if (site.customConfig.ga) { %> + + +<% } %> diff --git a/public/default-files/themes/fly/templates/post.ejs b/public/default-files/themes/fly/templates/post.ejs index bd739b9c..c9f23678 100644 --- a/public/default-files/themes/fly/templates/post.ejs +++ b/public/default-files/themes/fly/templates/post.ejs @@ -31,7 +31,7 @@ <% } %> -
+
<%- post.content %>
diff --git a/public/default-files/themes/notes/templates/post.ejs b/public/default-files/themes/notes/templates/post.ejs index 840debb9..6b7d2a35 100644 --- a/public/default-files/themes/notes/templates/post.ejs +++ b/public/default-files/themes/notes/templates/post.ejs @@ -4,7 +4,7 @@ - +
@@ -32,7 +32,7 @@ <% } %>
-
+
<%- post.content %>
diff --git a/public/default-files/themes/paper/templates/_blocks/head.ejs b/public/default-files/themes/paper/templates/_blocks/head.ejs index bc0ccaf0..96adcf15 100644 --- a/public/default-files/themes/paper/templates/_blocks/head.ejs +++ b/public/default-files/themes/paper/templates/_blocks/head.ejs @@ -1,36 +1,36 @@ - - -<%= siteTitle %> - - - - - - - - - - -<% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %> - <% if (commentSetting.commentPlatform === 'gitalk') { %> - - <% } %> - - <% if (commentSetting.commentPlatform === 'disqus') { %> - - <% } %> -<% } %> - - - - -<% if (site.customConfig.ga) { %> - - -<% } %> + + +<%= siteTitle %> + + + + + + + + + + +<% if (typeof commentSetting !== 'undefined' && commentSetting.showComment) { %> + <% if (commentSetting.commentPlatform === 'gitalk') { %> + + <% } %> + + <% if (commentSetting.commentPlatform === 'disqus') { %> + + <% } %> +<% } %> + + + + +<% if (site.customConfig.ga) { %> + + +<% } %> diff --git a/public/default-files/themes/paper/templates/post.ejs b/public/default-files/themes/paper/templates/post.ejs index af428f42..865376ad 100644 --- a/public/default-files/themes/paper/templates/post.ejs +++ b/public/default-files/themes/paper/templates/post.ejs @@ -22,7 +22,7 @@ <% if (post.feature) { %> <%= post.title %> <% } %> -
+
<%- post.content %>
diff --git a/public/default-files/themes/simple/templates/_blocks/scripts.ejs b/public/default-files/themes/simple/templates/_blocks/scripts.ejs index a94331e7..41472919 100644 --- a/public/default-files/themes/simple/templates/_blocks/scripts.ejs +++ b/public/default-files/themes/simple/templates/_blocks/scripts.ejs @@ -13,7 +13,7 @@ var app = new Vue({ <% if (site.customConfig.renderCode) { %> - + diff --git a/src/assets/styles/custom.less b/src/assets/styles/custom.less index 09fbb326..f28df9af 100644 --- a/src/assets/styles/custom.less +++ b/src/assets/styles/custom.less @@ -13,7 +13,7 @@ .ant-table-thead > tr > th { background: #ffffff; - color: #434343b0; + color: #1b1b18b0; font-weight: normal; } @@ -52,6 +52,9 @@ margin-left: 8px; outline: none; transition: all 0.3s; + display: flex; + justify-content: center; + align-items: center; i { font-weight: bold; } @@ -156,14 +159,14 @@ padding: 4px 8px; margin-bottom: 8px; color: #b1b1b1; - border-radius: 2px; + border-radius: 6px; transition: all 0.3s; &:hover { background: #efefef; } } .ant-tabs-tab-active { - color: #434343; + color: #1b1b18; } } @@ -206,6 +209,9 @@ background: #f3f7f9; margin: 12px; font-size: 14px; + display: inline-flex; + justify-content: center; + align-items: center; } .ant-modal-close { @@ -228,7 +234,7 @@ .ant-menu-inline .ant-menu-item, .ant-menu-inline .ant-menu-submenu-title { width: 90%; margin-left: 5%; - border-radius: 8px; + border-radius: 6px; color: #666; } diff --git a/src/assets/styles/var.less b/src/assets/styles/var.less index 8a8e079f..63f6acf9 100644 --- a/src/assets/styles/var.less +++ b/src/assets/styles/var.less @@ -1,15 +1,15 @@ -@primary-color: #555; -@primary-bg: #f7f6f6; -@danger-color: #fa5252; - -@border-radius-base : 2px; -@link-color: #1a5ccf; -@border-color: #e8e8e88a; - -@btn-default-border: #eaeaea; - -@label-color: #555; - -@font-family: PingFang SC,-apple-system,SF UI Text,Lucida Grande,STheiti,Microsoft YaHei,sans-serif; - +@primary-color: #1b1b18; +@primary-bg: #f7f6f6; +@danger-color: #fa5252; + +@border-radius-base : 6px; +@link-color: #1a5ccf; +@border-color: #e8e8e88a; + +@btn-default-border: #eaeaea; + +@label-color: #1b1b18; + +@font-family: PingFang SC,-apple-system,SF UI Text,Lucida Grande,STheiti,Microsoft YaHei,sans-serif; + @input-hover-border-color: #999; \ No newline at end of file diff --git a/src/components/Main.vue b/src/components/Main.vue index a0be6adf..f54b5855 100644 --- a/src/components/Main.vue +++ b/src/components/Main.vue @@ -262,6 +262,11 @@ export default class App extends Vue { public publish() { const { setting } = this.site + if (setting.platform === 'netlify' && !setting.netlifyAccessToken && !setting.netlifySiteId) { + this.$message.error(`🙁 ${this.$t('syncWarning')}`) + return false + } + if (!setting.branch && !setting.domain && !setting.token && !setting.repository) { this.$message.error(`🙁 ${this.$t('syncWarning')}`) return false @@ -373,6 +378,12 @@ export default class App extends Vue { /deep/ .ant-menu-item { padding-left: 16px !important; + transition: all 0.3s; + cursor: default; + + &:hover { + background-color: #fff; + } } /deep/ .ant-menu-vertical .ant-menu-item:after, .ant-menu-vertical-left .ant-menu-item:after, .ant-menu-vertical-right .ant-menu-item:after, .ant-menu-inline .ant-menu-item:after { diff --git a/src/components/PostsCard/Index.vue b/src/components/PostsCard/Index.vue index ccfc59e6..610bf6de 100644 --- a/src/components/PostsCard/Index.vue +++ b/src/components/PostsCard/Index.vue @@ -1,38 +1,38 @@ - - - - - + + + + + diff --git a/src/interfaces/setting.ts b/src/interfaces/setting.ts index 75924fbd..6bb73601 100644 --- a/src/interfaces/setting.ts +++ b/src/interfaces/setting.ts @@ -1,5 +1,5 @@ export interface ISetting { - platform: 'github' | 'coding' | 'sftp' + platform: 'github' | 'coding' | 'sftp' | 'gitee' | 'netlify' domain: string repository: string branch: string @@ -16,6 +16,8 @@ export interface ISetting { proxyPath: string proxyPort: string enabledProxy: 'direct' | 'proxy' + netlifyAccessToken: string + netlifySiteId: string [index: string]: string } diff --git a/src/server/app.ts b/src/server/app.ts index 99e7eabb..549b626a 100644 --- a/src/server/app.ts +++ b/src/server/app.ts @@ -31,12 +31,15 @@ export default class App { db: IApplicationDb + buildDir: string + constructor(setting: IApplicationSetting) { this.mainWindow = setting.mainWindow this.app = setting.app this.baseDir = setting.baseDir this.appDir = path.join(this.app.getPath('documents'), 'gridea') this.previewServer = setting.previewServer + this.buildDir = path.join(this.app.getPath('home'), '.gridea', 'output') this.db = { posts: [], @@ -81,6 +84,8 @@ export default class App { proxyPath: '', proxyPort: '', enabledProxy: 'direct', + netlifySiteId: '', + netlifyAccessToken: '', }, commentSetting: { showComment: false, @@ -202,6 +207,11 @@ export default class App { fse.writeFileSync(appConfigPath, jsonString) } + const buildDir = path.join(appConfigFolder, 'output') + if (!fse.pathExistsSync(buildDir)) { + fse.mkdirSync(buildDir) + } + const appConfig = fse.readJsonSync(appConfigPath) this.appDir = appConfig.sourceFolder diff --git a/src/server/deploy.ts b/src/server/deploy.ts index 4291592f..9929e7e4 100644 --- a/src/server/deploy.ts +++ b/src/server/deploy.ts @@ -7,7 +7,7 @@ import GitProxy from './plugins/deploys/gitproxy' const git = require('isomorphic-git') export default class Deploy extends Model { - outputDir: string = `${this.appDir}/output` + outputDir: string = this.buildDir remoteUrl = '' @@ -91,6 +91,7 @@ export default class Deploy extends Model { } async publish() { + await this.remoteDetect() this.db.themeConfig.domain = this.db.setting.domain let result = { success: true, @@ -112,61 +113,6 @@ export default class Deploy extends Model { return result } - async firstPush() { - const { setting } = this.db - const localBranchs = {} - console.log('first push') - - try { - await git.init({ fs, dir: this.outputDir }) - await git.setConfig({ - fs, - dir: this.outputDir, - path: 'user.name', - value: setting.username, - }) - await git.setConfig({ - fs, - dir: this.outputDir, - path: 'user.email', - value: setting.email, - }) - await git.add({ fs, dir: this.outputDir, filepath: '.' }) - await git.commit({ - fs, - dir: this.outputDir, - message: `update from gridea: ${moment().format('YYYY-MM-DD HH:mm:ss')}`, - }) - await git.addRemote({ - fs, dir: this.outputDir, remote: 'origin', url: this.remoteUrl, force: true, - }) - - await this.checkCurrentBranch() - const pushRes = await git.push({ - fs, - http: this.http, - dir: this.outputDir, - remote: 'origin', - ref: setting.branch, - force: true, - }) - return { - success: true, - data: pushRes, - message: '', - localBranchs, - } - } catch (e) { - console.error(e) - return { - success: false, - data: localBranchs, - message: e.message, - localBranchs, - } - } - } - async commonPush() { console.log('common push') const { setting } = this.db diff --git a/src/server/events/deploy.ts b/src/server/events/deploy.ts index 2b4b92ac..5f223960 100644 --- a/src/server/events/deploy.ts +++ b/src/server/events/deploy.ts @@ -2,6 +2,7 @@ import { ipcMain, IpcMainEvent } from 'electron' import Deploy from '../deploy' import Renderer from '../renderer' import SftpDeploy from '../plugins/deploys/sftp' +import NetlifyDeploy from '../plugins/deploys/netlify' export default class DeployEvents { constructor(appInstance: any) { @@ -10,6 +11,7 @@ export default class DeployEvents { const deploy = new Deploy(appInstance) const sftp = new SftpDeploy(appInstance) const renderer = new Renderer(appInstance) + const netlify = new NetlifyDeploy(appInstance) ipcMain.removeAllListeners('site-publish') ipcMain.removeAllListeners('site-published') @@ -23,6 +25,7 @@ export default class DeployEvents { 'coding': deploy, 'gitee': deploy, 'sftp': sftp, + 'netlify': netlify, } as any)[platform] // render @@ -40,6 +43,7 @@ export default class DeployEvents { 'coding': deploy, 'gitee': deploy, 'sftp': sftp, + 'netlify': netlify, } as any)[platform] const result = await client.remoteDetect() diff --git a/src/server/interfaces/application.ts b/src/server/interfaces/application.ts index 697f4c35..02417e40 100644 --- a/src/server/interfaces/application.ts +++ b/src/server/interfaces/application.ts @@ -30,5 +30,6 @@ export interface IApplication { app: any baseDir: string appDir: string + buildDir: string db: IApplicationDb } diff --git a/src/server/model.ts b/src/server/model.ts index f5c58169..b3664a8d 100644 --- a/src/server/model.ts +++ b/src/server/model.ts @@ -7,6 +7,8 @@ import { IApplicationDb, IApplication } from './interfaces/application' export default class Model { appDir: string + buildDir: string + $setting: any $posts: any @@ -19,6 +21,7 @@ export default class Model { constructor(appInstance: IApplication) { this.appDir = appInstance.appDir + this.buildDir = appInstance.buildDir this.db = appInstance.db this.mainWindow = appInstance.mainWindow diff --git a/src/server/plugins/deploys/netlify.ts b/src/server/plugins/deploys/netlify.ts new file mode 100644 index 00000000..c06c1307 --- /dev/null +++ b/src/server/plugins/deploys/netlify.ts @@ -0,0 +1,216 @@ +import fs from 'fs' +import path from 'path' +import axios from 'axios' +import normalizePath from 'normalize-path' +import crypto from 'crypto' +import util from 'util' +import Model from '../../model' +import { IApplication } from '../../interfaces/application' + +const asyncReadFile = util.promisify(fs.readFile) + +export default class NetlifyApi extends Model { + private apiUrl: string + + private accessToken: string + + private siteId: string + + private inputDir: string + + constructor(appInstance: IApplication) { + super(appInstance) + this.apiUrl = 'https://api.netlify.com/api/v1/' + this.accessToken = appInstance.db.setting.netlifyAccessToken + this.siteId = appInstance.db.setting.netlifySiteId + this.inputDir = appInstance.buildDir + } + + async request(method: 'GET' | 'PUT' | 'POST', endpoint: string, data?: any) { + const endpointUrl = this.apiUrl + endpoint.replace(':site_id', this.siteId) + const { setting } = this.db + const proxy = setting.enabledProxy ? { + host: setting.proxyPath, + port: Number(setting.proxyPort), + } : undefined + + return axios( + endpointUrl, + { + method, + headers: { + 'User-Agent': 'Gridea', + 'Authorization': `Bearer ${this.accessToken}`, + }, + data, + proxy, + }, + ) + } + + async remoteDetect() { + try { + const res = await this.request('GET', 'sites/:site_id/') + if (res.status === 200) { + return { + success: true, + message: res.data, + } + } + + return { + success: false, + message: res.data, + } + } catch (e) { + return { + success: false, + message: e, + } + } + } + + async publish() { + const result = { + success: true, + message: '同步成功', + data: null, + } + + try { + const localFilesList = await this.prepareLocalFilesList() + const deployData = await this.request('POST', 'sites/:site_id/deploys', localFilesList) + const deployId = deployData.data.id + const hashOfFilesToUpload = deployData.data.required + const filesToUpload = this.getFilesToUpload(localFilesList, hashOfFilesToUpload) + + for (let i = 0; i < filesToUpload.length; i += 1) { + const filePath = filesToUpload[i] + + try { + // eslint-disable-next-line no-await-in-loop + const res = await this.uploadFile(filePath, deployId) + + if (res.status === 422) { + return Promise.reject(res) + } + } catch (e) { + try { + // eslint-disable-next-line no-await-in-loop + const res = await this.uploadFile(filePath, deployId) + + if (res.status === 422) { + return Promise.reject(res) + } + } catch (error) { + return Promise.reject(error) + } + } + } + + return result + } catch (e) { + result.success = false + result.message = `[Server] 同步失败: ${e.message}` + } + } + + async prepareLocalFilesList() { + const tempFileList: any = this.readDirRecursiveSync(this.inputDir) + const fileList: any = {} + + for (const filePath of tempFileList) { + if (fs.lstatSync(path.join(this.inputDir, filePath)).isDirectory()) { + continue + } + + // eslint-disable-next-line no-await-in-loop + const fileHash = await this.getFileHash(path.join(this.inputDir, filePath)) + const fileKey = `/${filePath}`.replace(/\/\//gmi, '/') + fileList[fileKey] = fileHash + } + + return Promise.resolve({ files: fileList }) + } + + readDirRecursiveSync(dir: string, fileList?: any) { + const files = fs.readdirSync(dir) + fileList = fileList || [] + + files.forEach((file) => { + if (this.fileIsDirectory(dir, file)) { + fileList = this.readDirRecursiveSync(path.join(dir, file), fileList) + return + } + + if (this.fileIsNotExcluded(file)) { + fileList.push(this.getFilePath(dir, file)) + } + }) + + return fileList + } + + fileIsDirectory(dir: string, file: string) { + return fs.statSync(path.join(dir, file)).isDirectory() + } + + fileIsNotExcluded(file: string) { + return file.indexOf('.') !== 0 || file === '.htaccess' || file === '_redirects' + } + + getFilePath(dir: string, file: string, includeInputDir = false) { + if (!includeInputDir) { + dir = dir.replace(this.inputDir, '') + } + + return normalizePath(path.join(dir, file)) + } + + getFileHash(fileName: string) { + return new Promise((resolve, reject) => { + const shaSumCalculator = crypto.createHash('sha1') + + try { + const fileStream = fs.createReadStream(fileName) + fileStream.on('data', fileContentChunk => shaSumCalculator.update(fileContentChunk)) + fileStream.on('end', () => resolve(shaSumCalculator.digest('hex'))) + } catch (e) { + return reject(e) + } + }) + } + + getFilesToUpload(filesList: any, hashesToUpload: any) { + const filePaths = Object.keys(filesList.files) + const filesToUpload = [] + const foundedHashes = [] + + for (let i = 0; i < filePaths.length; i++) { + const filePath = filePaths[i] + + if (hashesToUpload.indexOf(filesList.files[filePath]) > -1) { + filesToUpload.push(filePath.replace(/\/\//gmi, '/')) + foundedHashes.push(filesList.files[filePath]) + } + } + + return filesToUpload + } + + async uploadFile(filePath: any, deployID: any) { + const endpointUrl = `${this.apiUrl}deploys/${deployID}/files${filePath}` + const fullFilePath = this.getFilePath(this.inputDir, filePath, true) + const fileContent = await asyncReadFile(fullFilePath) + + return axios(endpointUrl, { + method: 'PUT', + headers: { + 'User-Agent': 'Gridea', + 'Content-Type': 'application/octet-stream', + 'Authorization': `Bearer ${this.accessToken}`, + }, + data: fileContent, + }) + } +} diff --git a/src/server/renderer.ts b/src/server/renderer.ts index 04e7126f..b2a9b70a 100644 --- a/src/server/renderer.ts +++ b/src/server/renderer.ts @@ -23,7 +23,7 @@ Bluebird.promisifyAll(fs) const helper = new ContentHelper() export default class Renderer extends Model { - outputDir: string = `${this.appDir}/output` + outputDir: string = this.buildDir themePath: string = '' @@ -506,28 +506,32 @@ export default class Renderer extends Model { fse.ensureDirSync(cssFolderPath) const lessString = fs.readFileSync(lessFilePath, 'utf8') - less.render(lessString, { filename: lessFilePath }, async (err: any, cssString: Less.RenderOutput) => { - if (err) { - console.log(err) - } - let { css } = cssString - - // if have override - const customConfig = this.db.themeCustomConfig - const currentThemePath = urlJoin(this.appDir, 'themes', this.db.themeConfig.themeName) - - const styleOverridePath = urlJoin(currentThemePath, 'style-override.js') - const existOverrideFile = await fse.pathExists(styleOverridePath) - if (existOverrideFile) { - // clean cache - delete __non_webpack_require__.cache[__non_webpack_require__.resolve(styleOverridePath)] - - const generateOverride = __non_webpack_require__(styleOverridePath) - const customCss = generateOverride(customConfig) - css += customCss - } - - fs.writeFileSync(urlJoin(cssFolderPath, 'main.css'), css) + return new Promise((resolve, reject) => { + less.render(lessString, { filename: lessFilePath }, async (err: any, cssString: Less.RenderOutput) => { + if (err) { + console.log(err) + reject(err) + } + let { css } = cssString + + // if have override + const customConfig = this.db.themeCustomConfig + const currentThemePath = urlJoin(this.appDir, 'themes', this.db.themeConfig.themeName) + + const styleOverridePath = urlJoin(currentThemePath, 'style-override.js') + const existOverrideFile = await fse.pathExists(styleOverridePath) + if (existOverrideFile) { + // clean cache + delete __non_webpack_require__.cache[__non_webpack_require__.resolve(styleOverridePath)] + + const generateOverride = __non_webpack_require__(styleOverridePath) + const customCss = generateOverride(customConfig) + css += customCss + } + + fs.writeFileSync(urlJoin(cssFolderPath, 'main.css'), css) + resolve(true) + }) }) } @@ -622,17 +626,8 @@ export default class Renderer extends Model { } async clearOutputFolder() { - const { outputDir } = this - const files = fse.readdirSync(outputDir, { withFileTypes: true }) - const needClearPath = files - .map(item => item.name) - .filter(junk.not) - .filter((name: string) => name !== '.git') - try { - needClearPath.forEach(async (name: string) => { - fse.removeSync(urlJoin(outputDir, name)) - }) + fse.emptyDirSync(this.outputDir) } catch (e) { console.log('Delete file error', e) } diff --git a/src/store/modules/site.ts b/src/store/modules/site.ts index 74e4cab9..56b62511 100644 --- a/src/store/modules/site.ts +++ b/src/store/modules/site.ts @@ -65,6 +65,8 @@ const siteState: Site = { proxyPath: '', proxyPort: '', enabledProxy: 'direct', + netlifySiteId: '', + netlifyAccessToken: '', }, commentSetting: { showComment: false, diff --git a/src/views/article/ArticleUpdate.vue b/src/views/article/ArticleUpdate.vue index e79fbdcb..98fd4768 100644 --- a/src/views/article/ArticleUpdate.vue +++ b/src/views/article/ArticleUpdate.vue @@ -696,6 +696,9 @@ export default class ArticleUpdate extends Vue { border-radius: 20px; margin-left: 8px; outline: none; + display: flex; + justify-content: center; + align-items: center; transition: all 0.3s; i { font-weight: bold; diff --git a/src/views/article/Articles.vue b/src/views/article/Articles.vue index 066087a7..43cb25ec 100644 --- a/src/views/article/Articles.vue +++ b/src/views/article/Articles.vue @@ -1,267 +1,268 @@ - - - - - + + + + + diff --git a/src/views/setting/includes/BasicSetting.vue b/src/views/setting/includes/BasicSetting.vue index e0cbb649..079809f0 100644 --- a/src/views/setting/includes/BasicSetting.vue +++ b/src/views/setting/includes/BasicSetting.vue @@ -4,6 +4,7 @@ Github Pages + Netlify Coding Pages Gitee Pages SFTP @@ -18,6 +19,19 @@ +