mirror of
https://github.com/chen2226/obsidian-typecho.git
synced 2026-07-22 05:47:58 +00:00
发布文章别名使用hash,完善右侧浏览分类/标签/全部文章
This commit is contained in:
parent
84fcddffa7
commit
23bb70f699
11 changed files with 385 additions and 288 deletions
|
|
@ -34,6 +34,7 @@
|
|||
"domainName": "domain name",
|
||||
"category": "category",
|
||||
"typechoUser": "Typecho user, the user who uses the operation",
|
||||
"article": "article"
|
||||
"article": "article",
|
||||
"allArticle": "all article"
|
||||
}
|
||||
}
|
||||
|
|
@ -34,6 +34,7 @@
|
|||
"domainName": "域名",
|
||||
"category": "分类",
|
||||
"typechoUser": "Typecho 用户,操作时使用的用户",
|
||||
"article": "文章"
|
||||
"article": "文章",
|
||||
"allArticle": "全部文章"
|
||||
}
|
||||
}
|
||||
4
main.ts
4
main.ts
|
|
@ -1,7 +1,7 @@
|
|||
import { Plugin, addIcon } from "obsidian";
|
||||
import { settingTab } from "./setting/setting_tab";
|
||||
import { PushModal } from "./view/push";
|
||||
import { CategoryView, VIEW_TYPE as ArticleViewType } from "./view/categories";
|
||||
import { CategoryView, VIEW_TYPE as ArticleViewType } from "./view/article_type";
|
||||
import {
|
||||
TypechoPluginSettings,
|
||||
DEFAULT_SETTINGS,
|
||||
|
|
@ -11,7 +11,6 @@ let settings: TypechoPluginSettings;
|
|||
|
||||
export default class TypechoPlugin extends Plugin {
|
||||
async onload() {
|
||||
console.log("loading plugin");
|
||||
addIcon(
|
||||
"typecho",
|
||||
`<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1745386247342" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1491" xmlns:xlink="http://www.w3.org/1999/xlink"><path d="M517.04420574 882.47531656c-289.98573089 0-391.40007045-98.3302631-391.40007043-379.40894092 0-281.09368555 101.41433957-379.39393331 391.40007043-379.39393327 289.98573089 0 391.40007045 98.3002478 391.40007044 379.39393327 0 281.07867783-101.41433957 379.40143714-391.40007044 379.40143709zM306.28397227 386.32169964h421.50545924v-58.3648341H306.28397227v58.37233801z m0 145.92709302h301.07640006V473.8914624H306.28397227v58.36483409z m0 145.92709298h361.28717777v-58.37233793H306.2914761v58.37233793z" p-id="1492"></path></svg>`
|
||||
|
|
@ -33,7 +32,6 @@ export default class TypechoPlugin extends Plugin {
|
|||
}
|
||||
|
||||
onunload() {
|
||||
console.log("unloading plugin");
|
||||
this.app.workspace.getLeavesOfType(ArticleViewType);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
{
|
||||
"id": "typecho",
|
||||
"name": "Typecho",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.1",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Post the file to Typecho",
|
||||
"author": "Chen",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
"isDesktopOnly": false,
|
||||
"fundingUrl": "https://ko-fi.com/chen226"
|
||||
}
|
||||
329
styles.css
329
styles.css
|
|
@ -1,234 +1,234 @@
|
|||
/* 基础容器样式 */
|
||||
.tree-container {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
|
||||
sans-serif;
|
||||
color: #e0e0e0;
|
||||
padding: 8px;
|
||||
color: #e0e0e0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.tree-node-list {
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.tree-node-item {
|
||||
position: relative;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.tree-children {
|
||||
margin-top: 4px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.tree-node-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
user-select: none;
|
||||
padding: 6px 8px;
|
||||
border-radius: 6px;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.tree-node-wrapper:hover {
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
|
||||
.tree-toggle-icon {
|
||||
margin-right: 8px;
|
||||
font-size: 12px;
|
||||
color: #888;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.tree-toggle-icon.expanded {
|
||||
transform: rotate(90deg);
|
||||
color: #bbb;
|
||||
}
|
||||
|
||||
.tree-article-icon {
|
||||
margin-right: 6px;
|
||||
color: #66afee;
|
||||
font-size: 14px;
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.tree-node-wrapper:hover .tree-article-icon {
|
||||
color: #88caff;
|
||||
}
|
||||
|
||||
.tree-link-icon {
|
||||
margin-right: 6px;
|
||||
color: #3c9cf0;
|
||||
font-size: 14px;
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.tree-node-wrapper:hover .tree-link-icon {
|
||||
color: #008cff;
|
||||
}
|
||||
|
||||
.tree-node-label {
|
||||
font-size: 14px;
|
||||
flex: 1;
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.tree-indent {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.tree-indent::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 8px;
|
||||
width: 1px;
|
||||
background: #444;
|
||||
/* 树形结构基础样式 */
|
||||
.tree-node-list,
|
||||
.tree-container ul.tree-node-list {
|
||||
list-style: none;
|
||||
list-style-type: none;
|
||||
margin: 0;
|
||||
padding-left: 0;
|
||||
}
|
||||
|
||||
.tree-node-list ul {
|
||||
padding-left: 16px;
|
||||
position: relative;
|
||||
padding-left: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tree-node-list ul::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 8px;
|
||||
bottom: 0;
|
||||
width: 1px;
|
||||
background: #333;
|
||||
border-left: 1px dashed #444;
|
||||
background: #333;
|
||||
border-left: 1px dashed #444;
|
||||
bottom: 0;
|
||||
content: "";
|
||||
left: 8px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 1px;
|
||||
}
|
||||
|
||||
.tree-node-item {
|
||||
margin-bottom: 4px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tree-children {
|
||||
margin-top: 4px;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* 树节点包装器样式 */
|
||||
.tree-node-wrapper {
|
||||
align-items: center;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
padding: 6px 8px;
|
||||
transition: background-color 0.2s ease-in-out;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tree-node-wrapper:hover {
|
||||
background-color: #2a2a2a;
|
||||
}
|
||||
|
||||
/* 树形图标样式 */
|
||||
.tree-toggle-icon {
|
||||
color: #888;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
margin-right: 8px;
|
||||
transition: transform 0.2s ease-in-out;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.tree-toggle-icon.expanded {
|
||||
color: #bbb;
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.tree-article-icon,
|
||||
.tree-link-icon {
|
||||
font-size: 14px;
|
||||
margin-right: 6px;
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
.tree-article-icon {
|
||||
color: #66afee;
|
||||
}
|
||||
|
||||
.tree-link-icon {
|
||||
color: #3c9cf0;
|
||||
}
|
||||
|
||||
.tree-node-wrapper:hover .tree-article-icon {
|
||||
color: #88caff;
|
||||
}
|
||||
|
||||
.tree-node-wrapper:hover .tree-link-icon {
|
||||
color: #008cff;
|
||||
}
|
||||
|
||||
.tree-node-label {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
transition: color 0.2s ease-in-out;
|
||||
}
|
||||
|
||||
/* 图标样式 */
|
||||
.tree-link-icon svg,
|
||||
.tree-article-icon.inline-icon svg {
|
||||
cursor: pointer;
|
||||
height: 12px;
|
||||
margin-left: 4px;
|
||||
vertical-align: middle;
|
||||
width: 12px;
|
||||
}
|
||||
|
||||
/* 文章浏览模态框样式 */
|
||||
.browse-articles-modal {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
margin: 0 auto;
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.browse-articles-modal .browse-article-item {
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
transition: background-color 0.2s;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
padding: 1rem;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.browse-articles-modal .browse-article-item:hover {
|
||||
background-color: var(--background-secondary);
|
||||
background-color: var(--background-secondary);
|
||||
}
|
||||
|
||||
.browse-articles-modal .article-title {
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 1.1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.browse-articles-modal .article-title .download-icon {
|
||||
margin-left: 1rem;
|
||||
font-size: 0.9rem;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.browse-articles-modal .article-title a {
|
||||
color: var(--text-link);
|
||||
text-decoration: none;
|
||||
color: var(--text-link);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.browse-articles-modal .article-date {
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9em;
|
||||
color: var(--text-muted);
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
.browse-articles-modal .pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-top: 1.5rem;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.browse-articles-modal .pagination button {
|
||||
padding: 0.4rem 0.8rem;
|
||||
border-radius: var(--radius);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
background-color: var(--background-primary);
|
||||
color: var(--text-normal);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
background-color: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius);
|
||||
color: var(--text-normal);
|
||||
cursor: pointer;
|
||||
padding: 0.4rem 0.8rem;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.browse-articles-modal .pagination button:hover:not(:disabled) {
|
||||
background-color: var(--interactive-accent);
|
||||
color: var(--background-primary);
|
||||
border-color: var(--interactive-accent);
|
||||
background-color: var(--interactive-accent);
|
||||
border-color: var(--interactive-accent);
|
||||
color: var(--background-primary);
|
||||
}
|
||||
|
||||
.browse-articles-modal .pagination button:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
cursor: not-allowed;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.browse-articles-modal .no-articles {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: var(--text-muted);
|
||||
color: var(--text-muted);
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 预览弹窗样式 */
|
||||
.preview-popup {
|
||||
position: absolute;
|
||||
background-color: var(--background-secondary);
|
||||
color: var(--text-normal);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: var(--radius);
|
||||
padding: 0.75rem 1rem;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
z-index: 9999;
|
||||
color: var(--text-normal);
|
||||
font-size: 0.9em;
|
||||
max-width: 300px;
|
||||
word-break: break-word;
|
||||
padding: 0.75rem 1rem;
|
||||
position: absolute;
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
word-break: break-word;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.preview-popup::after {
|
||||
border-color: transparent transparent var(--background-secondary) transparent;
|
||||
border-style: solid;
|
||||
border-width: 0 6px 6px 6px;
|
||||
content: "";
|
||||
left: 16px;
|
||||
position: absolute;
|
||||
top: -6px;
|
||||
left: 16px;
|
||||
border-width: 0 6px 6px 6px;
|
||||
border-style: solid;
|
||||
border-color: transparent transparent var(--background-secondary) transparent;
|
||||
}
|
||||
|
||||
/* 文章预览模态框样式 */
|
||||
.article-preview-modal {
|
||||
max-width: 80vw;
|
||||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
max-width: 80vw;
|
||||
opacity: 1;
|
||||
overflow: auto;
|
||||
transform: scale(1);
|
||||
transition: opacity 0.3s ease, transform 0.3s ease;
|
||||
}
|
||||
|
||||
/* 初始状态:透明 + 缩小 */
|
||||
.article-preview-modal.fade-enter {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* 进入动画激活态 */
|
||||
.article-preview-modal.fade-enter-active {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
/* 退出动画激活态 */
|
||||
.article-preview-modal.fade-exit {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
|
|
@ -238,3 +238,30 @@
|
|||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
/* 标签容器样式 */
|
||||
.tab-container {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.tab-container .selected {
|
||||
background: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
.tab-span {
|
||||
background: var(--background-secondary);
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.tab-span:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
.tab-span.active {
|
||||
background: var(--background-modifier-active);
|
||||
}
|
||||
|
|
@ -65,13 +65,10 @@ export class HttpUtils {
|
|||
headers: headers || {},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
};
|
||||
|
||||
console.log(requestOptions);
|
||||
try {
|
||||
const response: RequestUrlResponse = await requestUrl(
|
||||
requestOptions
|
||||
);
|
||||
console.log(response.json);
|
||||
const res: ResponseType = response.json;
|
||||
if (res.status === "success") {
|
||||
return res;
|
||||
|
|
|
|||
163
utils/util.ts
163
utils/util.ts
|
|
@ -1,79 +1,98 @@
|
|||
// 定义树节点接口
|
||||
export interface TreeNode {
|
||||
id?: number;
|
||||
[key: string]: any;
|
||||
children: TreeNode[];
|
||||
parent?: TreeNode;
|
||||
id?: number;
|
||||
[key: string]: any;
|
||||
children: TreeNode[];
|
||||
parent?: TreeNode;
|
||||
}
|
||||
|
||||
export const Util = {
|
||||
// tree
|
||||
treeUtil: {
|
||||
generateTreeData: function<T extends Record<string, any>>(
|
||||
data: T[],
|
||||
nodeId: keyof T,
|
||||
parentNodeKey: keyof T
|
||||
): TreeNode[] {
|
||||
// 将数据存储为以 nodeId 为 KEY 的 map 索引数据列
|
||||
const map: Record<string | number, TreeNode> = {};
|
||||
data.forEach((item) => {
|
||||
(item as unknown as TreeNode).children = [];
|
||||
map[item[nodeId] as string | number] = item as unknown as TreeNode;
|
||||
});
|
||||
// tree
|
||||
treeUtil: {
|
||||
generateTreeData: function <T extends Record<string, any>>(
|
||||
data: T[],
|
||||
nodeId: keyof T,
|
||||
parentNodeKey: keyof T
|
||||
): TreeNode[] {
|
||||
// 将数据存储为以 nodeId 为 KEY 的 map 索引数据列
|
||||
const map: Record<string | number, TreeNode> = {};
|
||||
data.forEach((item) => {
|
||||
(item as unknown as TreeNode).children = [];
|
||||
map[item[nodeId] as string | number] =
|
||||
item as unknown as TreeNode;
|
||||
});
|
||||
|
||||
// 遍历数据,构建树形结构
|
||||
const rootNodes: TreeNode[] = [];
|
||||
data.forEach((item) => {
|
||||
const parent = map[item[parentNodeKey] as string | number];
|
||||
if (parent) {
|
||||
parent.children.push(item as unknown as TreeNode);
|
||||
(item as unknown as TreeNode).parent = parent;
|
||||
} else {
|
||||
rootNodes.push(item as unknown as TreeNode);
|
||||
}
|
||||
});
|
||||
// 遍历数据,构建树形结构
|
||||
const rootNodes: TreeNode[] = [];
|
||||
data.forEach((item) => {
|
||||
const parent = map[item[parentNodeKey] as string | number];
|
||||
if (parent) {
|
||||
parent.children.push(item as unknown as TreeNode);
|
||||
(item as unknown as TreeNode).parent = parent;
|
||||
} else {
|
||||
rootNodes.push(item as unknown as TreeNode);
|
||||
}
|
||||
});
|
||||
|
||||
return rootNodes;
|
||||
},
|
||||
// 在树形结构中查找指定id节点
|
||||
findNodeInTree: function(tree: TreeNode[], id: number): TreeNode | undefined {
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
if (tree[i].id === id) {
|
||||
return tree[i];
|
||||
} else {
|
||||
if (tree[i].children) {
|
||||
const node = this.findNodeInTree(tree[i].children, id);
|
||||
if (node) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
// 在树形结构中查找指定id节点的上一个父节点
|
||||
findParentNodeInTree: function(tree: TreeNode[], id: number): TreeNode | null {
|
||||
// Id等于0的节点为根节点,没有父节点
|
||||
// id找到了的话,往上找一层父节点返回
|
||||
if (id === 0) {
|
||||
return null;
|
||||
}
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
if (tree[i].id === id) {
|
||||
return tree[i].parent || null;
|
||||
} else {
|
||||
if (tree[i].children) {
|
||||
const node = this.findParentNodeInTree(
|
||||
tree[i].children,
|
||||
id
|
||||
);
|
||||
if (node) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
};
|
||||
return rootNodes;
|
||||
},
|
||||
// 在树形结构中查找指定id节点
|
||||
findNodeInTree: function (
|
||||
tree: TreeNode[],
|
||||
id: number
|
||||
): TreeNode | undefined {
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
if (tree[i].id === id) {
|
||||
return tree[i];
|
||||
} else {
|
||||
if (tree[i].children) {
|
||||
const node = this.findNodeInTree(tree[i].children, id);
|
||||
if (node) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
// 在树形结构中查找指定id节点的上一个父节点
|
||||
findParentNodeInTree: function (
|
||||
tree: TreeNode[],
|
||||
id: number
|
||||
): TreeNode | null {
|
||||
// Id等于0的节点为根节点,没有父节点
|
||||
// id找到了的话,往上找一层父节点返回
|
||||
if (id === 0) {
|
||||
return null;
|
||||
}
|
||||
for (let i = 0; i < tree.length; i++) {
|
||||
if (tree[i].id === id) {
|
||||
return tree[i].parent || null;
|
||||
} else {
|
||||
if (tree[i].children) {
|
||||
const node = this.findParentNodeInTree(
|
||||
tree[i].children,
|
||||
id
|
||||
);
|
||||
if (node) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
},
|
||||
hash: {
|
||||
simpleHash(str: string) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
const char = str.charCodeAt(i);
|
||||
hash = (hash << 5) - hash + char;
|
||||
hash |= 0; // Convert to 32bit integer
|
||||
}
|
||||
// 转成16进制并取最后6位
|
||||
return (hash >>> 0).toString(16).padStart(8, "0").slice(-6);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
{
|
||||
"0.1.0": "0.15.0"
|
||||
"0.1.0": "0.15.0",
|
||||
"0.1.1": "0.15.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ import { HttpUtils } from "../utils/request";
|
|||
import { Util } from "../utils/util";
|
||||
import { BrowseArticles } from "./browse_articles";
|
||||
|
||||
const VIEW_TYPE = "category-view";
|
||||
const VIEW_TYPE = "article-view";
|
||||
|
||||
class CategoryView extends ItemView {
|
||||
getViewType(): string {
|
||||
return VIEW_TYPE;
|
||||
|
|
@ -24,46 +25,98 @@ class CategoryView extends ItemView {
|
|||
const container = this.containerEl.children[1];
|
||||
container.empty();
|
||||
|
||||
// 标题
|
||||
container.createEl("h4", { text: i18n.t("field.category") });
|
||||
// 创建选项卡导航
|
||||
const tabContainer = container.createDiv({ cls: "tab-container" });
|
||||
const tabs = [
|
||||
{ key: "all", label: i18n.t("field.allArticle") },
|
||||
{ key: "category", label: i18n.t("field.category") },
|
||||
{ key: "tag", label: i18n.t("field.tag") },
|
||||
];
|
||||
// 初始化加载第一个 tab
|
||||
this.currentTab = "category";
|
||||
|
||||
const refreshButton = container.createEl("button", {
|
||||
tabs.forEach((tab) => {
|
||||
const tabEl = tabContainer.createEl("span", {
|
||||
text: tab.label,
|
||||
cls:
|
||||
"tab-span " +
|
||||
(tab.key === this.currentTab ? "selected" : ""),
|
||||
});
|
||||
tabEl.addEventListener("click", () => {
|
||||
if (tab.key == "all") {
|
||||
new BrowseArticles(
|
||||
this.app,
|
||||
{ name: i18n.t("field.allArticle") },
|
||||
'all'
|
||||
).open();
|
||||
return;
|
||||
}
|
||||
this.switchTab(tab.key, tabContainer, tabEl);
|
||||
});
|
||||
});
|
||||
|
||||
// 内容容器
|
||||
this.contentContainer = container.createDiv({ cls: "tab-content" });
|
||||
|
||||
// 刷新按钮
|
||||
const refreshButton = tabContainer.createEl("button", {
|
||||
cls: "refresh-button",
|
||||
});
|
||||
setIcon(refreshButton, "rotate-ccw");
|
||||
refreshButton.addEventListener("click", () => {
|
||||
this.initCategory(container);
|
||||
this.loadData(this.currentTab, this.contentContainer);
|
||||
});
|
||||
|
||||
this.initCategory(container);
|
||||
this.loadData("category", this.contentContainer);
|
||||
}
|
||||
|
||||
async initCategory(container: Element) {
|
||||
for (let i = 0; i < container.children.length; i++) {
|
||||
if (i != 0 && i != 1) {
|
||||
container.children[i].remove();
|
||||
}
|
||||
}
|
||||
switchTab(tabKey: string, tabContainer: HTMLElement, tabEl: HTMLElement) {
|
||||
const selectedTabs =
|
||||
tabContainer.querySelectorAll(".tab-span.selected");
|
||||
selectedTabs.forEach((tab: HTMLElement) => {
|
||||
tab.classList.remove("selected");
|
||||
});
|
||||
tabEl.classList.add("selected");
|
||||
this.currentTab = tabKey;
|
||||
this.contentContainer.empty();
|
||||
this.loadData(tabKey, this.contentContainer);
|
||||
}
|
||||
|
||||
currentTab: string;
|
||||
contentContainer: HTMLElement;
|
||||
|
||||
async loadData(tabKey: string, container: Element) {
|
||||
container.empty();
|
||||
|
||||
// 校验是否选择操作用户
|
||||
if (!getSettings().User) {
|
||||
container.createEl("p", {
|
||||
text: i18n.t("error.noTypechoUser"),
|
||||
});
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
let data: any[] = [];
|
||||
|
||||
if (tabKey === "category") {
|
||||
const response = await HttpUtils.get("/categories", {});
|
||||
const data = Util.treeUtil.generateTreeData(
|
||||
data = Util.treeUtil.generateTreeData(
|
||||
response.data,
|
||||
"mid",
|
||||
"parent"
|
||||
);
|
||||
} else if (tabKey === "tag") {
|
||||
const response = await HttpUtils.get("/tags", {});
|
||||
data = Util.treeUtil.generateTreeData(
|
||||
response.data,
|
||||
"mid",
|
||||
"parent"
|
||||
);
|
||||
// 渲染树结构
|
||||
const treeContainer = container.createDiv({
|
||||
cls: "tree-container",
|
||||
});
|
||||
this.renderTree(treeContainer, data);
|
||||
}
|
||||
|
||||
const treeContainer = container.createDiv({ cls: "tree-container" });
|
||||
this.renderTree(treeContainer, data);
|
||||
}
|
||||
|
||||
renderTree(container: Element, nodes: any[], depth = 0) {
|
||||
const ul = container.createEl("ul", { cls: "tree-node-list" });
|
||||
|
||||
|
|
@ -71,11 +124,9 @@ class CategoryView extends ItemView {
|
|||
const li = ul.createEl("li");
|
||||
const wrapper = li.createDiv({ cls: "tree-node-wrapper" });
|
||||
|
||||
// 缩进
|
||||
const indent = wrapper.createSpan({ cls: "tree-indent" });
|
||||
indent.style.paddingLeft = `${depth * 16}px`;
|
||||
|
||||
// 判断并添加展开/折叠图标
|
||||
if (node.children && node.children.length > 0) {
|
||||
const toggleIcon = wrapper.createEl("span", {
|
||||
text: node.isExpanded ? "▼" : "▶",
|
||||
|
|
@ -105,21 +156,11 @@ class CategoryView extends ItemView {
|
|||
});
|
||||
}
|
||||
|
||||
const linkIcon = wrapper.createEl("span", {
|
||||
cls: "tree-link-icon",
|
||||
});
|
||||
setIcon(linkIcon, "link");
|
||||
linkIcon.addEventListener("click", (e) => {
|
||||
e.stopPropagation();
|
||||
window.open(node.url);
|
||||
});
|
||||
// 显示节点标签
|
||||
wrapper.createEl("span", {
|
||||
text: node.name,
|
||||
cls: "tree-node-label",
|
||||
});
|
||||
|
||||
// 判断并添加文件图标(仅当 count > 0)
|
||||
if (node.count && node.count > 0) {
|
||||
const articleIcon = wrapper.createEl("span", {
|
||||
cls: "tree-article-icon",
|
||||
|
|
@ -127,11 +168,11 @@ class CategoryView extends ItemView {
|
|||
setIcon(articleIcon, "newspaper");
|
||||
|
||||
articleIcon.addEventListener("click", (e) => {
|
||||
new BrowseArticles(this.app, node).open();
|
||||
e.stopPropagation();
|
||||
new BrowseArticles(this.app, node, this.currentTab).open();
|
||||
});
|
||||
}
|
||||
|
||||
// 初始展开
|
||||
if (node.children && node.children.length > 0 && node.isExpanded) {
|
||||
const childContainer = li.createEl("div", {
|
||||
cls: "tree-children",
|
||||
|
|
@ -8,10 +8,12 @@ export class BrowseArticles extends Modal {
|
|||
currentPage = 1;
|
||||
totalPages = 1;
|
||||
contentContainer: HTMLElement;
|
||||
type: string;
|
||||
|
||||
constructor(app: App, data: any) {
|
||||
constructor(app: App, data: any,type: string) {
|
||||
super(app);
|
||||
this.data = data;
|
||||
this.type = type;
|
||||
this.modalEl.addClass("browse-articles-modal");
|
||||
}
|
||||
|
||||
|
|
@ -41,8 +43,8 @@ export class BrowseArticles extends Modal {
|
|||
const params = {
|
||||
page: page.toString(),
|
||||
pageSize: "10",
|
||||
filterType: "category",
|
||||
filterSlug: this.data.slug,
|
||||
filterType: this.type ?? '',
|
||||
filterSlug: this.data.slug ?? '',
|
||||
};
|
||||
|
||||
let url = "/posts?";
|
||||
|
|
@ -89,12 +91,28 @@ export class BrowseArticles extends Modal {
|
|||
// 保存本地根目录
|
||||
const rootPath = this.app.vault.getRoot().path;
|
||||
const fileName = `${post.title}.md`;
|
||||
const filePath = `${rootPath}/${fileName}`;
|
||||
this.app.vault.create(filePath, content)
|
||||
.then(() => {
|
||||
console.log('ok')
|
||||
// this.app.workspace.openLinkText(filePath, "");
|
||||
});
|
||||
const filePath = `${rootPath}${fileName}`;
|
||||
|
||||
let file = this.app.vault.getFileByPath(fileName);
|
||||
if (file) {
|
||||
await this.app.vault.modify(file, content);
|
||||
} else {
|
||||
await this.app.vault
|
||||
.create(filePath, content)
|
||||
.then((newfile) => {
|
||||
file = newfile;
|
||||
});
|
||||
}
|
||||
|
||||
// 打开窗口浏览文件
|
||||
if (file) {
|
||||
this.app.workspace.openLinkText(
|
||||
file.path,
|
||||
"",
|
||||
true
|
||||
);
|
||||
}
|
||||
this.close();
|
||||
});
|
||||
|
||||
// 点击预览内容
|
||||
|
|
|
|||
13
view/push.ts
13
view/push.ts
|
|
@ -3,6 +3,7 @@ import { getSettings } from "../main";
|
|||
import { addMetas } from "./add_metasl";
|
||||
import { HttpUtils } from "../utils/request";
|
||||
import i18n from "../utils/i18n";
|
||||
import { Util } from "../utils/util";
|
||||
export class PushModal extends Modal {
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
|
|
@ -91,20 +92,15 @@ export class PushModal extends Modal {
|
|||
}
|
||||
this.notice = new Notice(`${i18n.t("sync.publish")}...`);
|
||||
try {
|
||||
// 获取csrfToken
|
||||
const token = await HttpUtils.get(
|
||||
"/getCsrfToken?key=" + this.title
|
||||
);
|
||||
const mid = [...this.selectedCategories, ...this.selectedTags];
|
||||
|
||||
const data = {
|
||||
title: this.title,
|
||||
text: "<!--markdown-->" + this.content,
|
||||
authorId: getSettings().User?.uid,
|
||||
mid: mid.join(","),
|
||||
slug: "obsidian-" + this.baseFileName,
|
||||
token: token.data.csrfToken,
|
||||
slug: "obsidian-" + Util.hash.simpleHash(this.baseFileName),
|
||||
};
|
||||
console.log(data);
|
||||
const response = await HttpUtils.post("/postArticle", data);
|
||||
if (response.status === "success") {
|
||||
new Notice(
|
||||
|
|
@ -277,7 +273,6 @@ export class PushModal extends Modal {
|
|||
this.selectedCategories.splice(index, 1); // 从选中列表移除
|
||||
}
|
||||
}
|
||||
console.log("当前选中的分类:", this.selectedCategories);
|
||||
});
|
||||
|
||||
label.addEventListener("click", (e) => {
|
||||
|
|
@ -346,7 +341,6 @@ export class PushModal extends Modal {
|
|||
this.selectedTags.splice(index, 1);
|
||||
}
|
||||
}
|
||||
console.log("当前选中的标签:", this.selectedTags);
|
||||
});
|
||||
|
||||
label.addEventListener("click", (e) => {
|
||||
|
|
@ -373,7 +367,6 @@ export class PushModal extends Modal {
|
|||
: this.getCategories(contentEl);
|
||||
};
|
||||
button.addEventListener("click", async () => {
|
||||
console.log(type);
|
||||
new addMetas(this.app, type, fun).open();
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue