Skip to content

Commit 1681e5d

Browse files
authored
Merge pull request #670 from Qexo/dev
3.7.0
2 parents 7cd2490 + 0e8d21d commit 1681e5d

File tree

11 files changed

+270
-78
lines changed

11 files changed

+270
-78
lines changed

core/qexoSettings.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import json
22
import random
33

4-
QEXO_VERSION = "3.6.3"
4+
QEXO_VERSION = "3.7.0"
55
QEXO_STATIC = "3.0.5"
66

77
DEFAULT_EMOJI = {"微笑": "🙂", "撇嘴": "😦", "色": "😍", "发呆": "😍", "得意": "😎",

hexoweb/libs/platforms/configs/__init__.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,14 @@
44
from . import vuepress
55
from . import vitepress
66
from . import nuxt
7+
from . import astro
78

89
_all_configs = {
910
hexo.config["name"]: hexo.config,
1011
hugo.config["name"]: hugo.config,
1112
valaxy.config["name"]: valaxy.config,
1213
vuepress.config["name"]: vuepress.config,
1314
vitepress.config["name"]: vitepress.config,
14-
nuxt.config["name"]: nuxt.config
15+
nuxt.config["name"]: nuxt.config,
16+
astro.config["name"]: astro.config
1517
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# https://astro.build/
2+
# 为 Astro 提供平台配置,仅识别 Markdown (.md) 和 HTML (.html) 文件
3+
config = {
4+
"name": "Astro",
5+
"posts": {
6+
"path": ["src/content", "content", "src/pages", "src/pages/blog"],
7+
"depth": [-1, -1, -1, -1],
8+
"type": [".md", ".html"],
9+
"save_path": "src/content/${filename}.md",
10+
"scaffold": "scaffolds/post.md"
11+
},
12+
"drafts": {
13+
"path": ["src/drafts", "drafts"],
14+
"depth": [-1, -1],
15+
"type": [".md", ".html"],
16+
"save_path": "src/drafts/${filename}.md",
17+
"scaffold": "scaffolds/draft.md"
18+
},
19+
"pages": {
20+
"path": ["src/pages"],
21+
"depth": [-1],
22+
"type": [".md", ".html"],
23+
"save_path": "src/pages/${filename}.md",
24+
"scaffold": "scaffolds/page.md",
25+
"excludes": ["public", "components"]
26+
},
27+
"configs": {
28+
"path": ["", ".github", "src", "public"],
29+
"depth": [1, 3, 2, 1],
30+
"type": [".mjs", ".js", ".ts", ".json", ".toml", ".yaml", ".yml"]
31+
}
32+
}
33+
34+
# 导出声明放在 config 之后,避免静态分析器警告

hexoweb/libs/platforms/configs/hexo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"pages": {
1919
"path": ["source"],
2020
"depth": [2],
21-
"type": ["index.md", "index.html", ".md", ".html"],
21+
"type": [".md", ".html"],
2222
"save_path": "source/${filename}.md",
2323
"scaffold": "scaffolds/page.md",
2424
"excludes": ["_posts", "_drafts"]

hexoweb/libs/platforms/configs/hugo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"pages": {
1919
"path": ["content"],
2020
"depth": [3],
21-
"type": ["_index.md", "index.html", "index.md", ".md", ".html"],
21+
"type": [".md", ".html"],
2222
"save_path": "content/${filename}/index.md",
2323
"scaffold": "archetypes/page.md",
2424
"excludes": ["post", "_drafts"]

hexoweb/libs/platforms/configs/nuxt.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"pages": {
1919
"path": ["content"],
2020
"depth": [1],
21-
"type": ["index.md", "index.html", ".md", ".html"],
21+
"type": [".md", ".html"],
2222
"save_path": "content/${filename}.md",
2323
"scaffold": "scaffolds/page.md",
2424
"excludes": ["content/posts"]

hexoweb/libs/platforms/configs/valaxy.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"pages": {
1919
"path": ["pages"],
2020
"depth": [2],
21-
"type": ["index.md", "index.html", ".md", ".html"],
21+
"type": [".md", ".html"],
2222
"save_path": "pages/${filename}",
2323
"scaffold": "scaffolds/page.md",
2424
"excludes": ["posts", "_drafts"]

hexoweb/libs/platforms/core.py

Lines changed: 104 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ def get_content(self, file):
1313
...
1414

1515
def get_path(self, path):
16-
...
16+
"""
17+
Abstract: should be implemented by subclasses. Return a dict with 'data' key.
18+
Default returns empty directory.
19+
"""
20+
return {"data": []}
1721

1822
def save(self, file, content, commitchange="Update by Qexo", autobuild=True):
1923
...
@@ -54,60 +58,63 @@ def get_posts(self):
5458
drafts = self.get_tree(
5559
self.config["drafts"]["path"][path_index], self.config["drafts"]["depth"][path_index],
5660
self.config["drafts"].get("excludes"))
57-
for i in range(len(drafts)):
58-
flag = False
59-
for j in self.config["drafts"]["type"]:
60-
if drafts[i]["path"].endswith(j):
61-
flag = j
61+
for post in drafts:
62+
if post["type"] != "file":
63+
continue
64+
matched_ext = None
65+
for ext in self.config["drafts"]["type"]:
66+
if post["path"].endswith(ext):
67+
matched_ext = ext
6268
break
63-
if drafts[i]["type"] == "file" and flag:
64-
if self.config["drafts"]["path"][path_index] == "":
65-
name = drafts[i]["path"]
66-
else:
67-
name = drafts[i]["path"].split(
68-
self.config["drafts"]["path"][path_index] if self.config["drafts"]["path"][path_index][-1] == "/" else
69-
self.config["drafts"]["path"][path_index] + "/")[1]
70-
name = name[:-len(flag) - (1 if name[-1] == "/" else 0)]
71-
if name.endswith("/"):
72-
name = name[:-1]
73-
_drafts.append({"name": name,
74-
"fullname": name + flag,
75-
"path": drafts[i]["path"],
76-
"size": drafts[i]["size"],
77-
"status": False})
78-
79-
names.append(drafts[i]["path"].split(
80-
self.config["drafts"]["path"][path_index])[1])
69+
if not matched_ext:
70+
continue
71+
rel_path = post["path"]
72+
prefix = self.config["drafts"]["path"][path_index]
73+
if prefix:
74+
prefix_slash = prefix if prefix.endswith("/") else prefix + "/"
75+
name = rel_path[len(prefix_slash):] if rel_path.startswith(prefix_slash) else rel_path
76+
else:
77+
name = rel_path
78+
name = name.rstrip("/")
79+
name_no_ext = name[:-len(matched_ext)]
80+
_drafts.append({"name": name_no_ext,
81+
"fullname": name_no_ext + matched_ext,
82+
"path": post["path"],
83+
"size": post["size"],
84+
"status": False})
85+
names.append(name_no_ext + matched_ext)
8186
except Exception as e:
8287
logging.error("读取草稿目录 {} 错误: {},跳过".format(self.config["drafts"]["path"][path_index], repr(e)))
8388
for path_index in range(len(self.config["posts"]["path"])):
8489
try:
8590
posts = self.get_tree(
8691
self.config["posts"]["path"][path_index], self.config["posts"]["depth"][path_index],
8792
self.config["posts"].get("excludes"))
88-
for i in range(len(posts)):
89-
flag = False
90-
for j in self.config["posts"]["type"]:
91-
if posts[i]["path"].endswith(j):
92-
flag = j
93+
for post in posts:
94+
if post["type"] != "file":
95+
continue
96+
matched_ext = None
97+
for ext in self.config["posts"]["type"]:
98+
if post["path"].endswith(ext):
99+
matched_ext = ext
93100
break
94-
if posts[i]["type"] == "file" and flag:
95-
if self.config["posts"]["path"][path_index] == "":
96-
name = posts[i]["path"]
97-
else:
98-
name = posts[i]["path"].split(
99-
self.config["posts"]["path"][path_index] if self.config["posts"]["path"][path_index][-1] == "/" else
100-
self.config["posts"]["path"][path_index] + "/")[1]
101-
name = name[:-len(flag) - (1 if name[-1] == "/" else 0)]
102-
if name.endswith("/"):
103-
name = name[:-1]
104-
_posts.append({"name": name,
105-
"fullname": name + flag,
106-
"path": posts[i]["path"],
107-
"size": posts[i]["size"],
108-
"status": True})
109-
names.append(posts[i]["path"].split(
110-
self.config["posts"]["path"][path_index])[1])
101+
if not matched_ext:
102+
continue
103+
rel_path = post["path"]
104+
prefix = self.config["posts"]["path"][path_index]
105+
if prefix:
106+
prefix_slash = prefix if prefix.endswith("/") else prefix + "/"
107+
name = rel_path[len(prefix_slash):] if rel_path.startswith(prefix_slash) else rel_path
108+
else:
109+
name = rel_path
110+
name = name.rstrip("/")
111+
name_no_ext = name[:-len(matched_ext)]
112+
_posts.append({"name": name_no_ext,
113+
"fullname": name_no_ext + matched_ext,
114+
"path": post["path"],
115+
"size": post["size"],
116+
"status": True})
117+
names.append(name_no_ext + matched_ext)
111118
except Exception as e:
112119
logging.error("读取已发布目录 {} 错误: {},跳过".format(self.config["posts"]["path"][path_index], repr(e)))
113120
posts = _posts + _drafts
@@ -122,24 +129,35 @@ def get_pages(self):
122129
self.config["pages"]["path"][path_index], self.config["pages"]["depth"][path_index],
123130
self.config["pages"].get("excludes"))
124131
for post in posts:
125-
flag = False
126-
for i in self.config["pages"]["type"]:
127-
if post["path"].endswith(i):
128-
flag = i
132+
# only files with configured extensions
133+
if post["type"] != "file":
134+
continue
135+
matched_ext = None
136+
for ext in self.config["pages"]["type"]:
137+
if post["path"].endswith(ext):
138+
matched_ext = ext
129139
break
130-
if post["type"] == "file" and flag:
131-
if self.config["pages"]["path"][path_index] == "":
132-
name = post["path"]
140+
if not matched_ext:
141+
continue
142+
# remove prefix and extension to get display name
143+
rel_path = post["path"]
144+
prefix = self.config["pages"]["path"][path_index]
145+
if prefix:
146+
prefix_slash = prefix if prefix.endswith("/") else prefix + "/"
147+
if rel_path.startswith(prefix_slash):
148+
name = rel_path[len(prefix_slash):]
133149
else:
134-
name = post["path"].split(
135-
self.config["pages"]["path"][path_index] if self.config["pages"]["path"][path_index][-1] == "/" else
136-
self.config["pages"]["path"][path_index] + "/")[1]
137-
name = name[:-len(flag) - (1 if name[-1] == "/" else 0)]
138-
if name.endswith("/"):
139-
name = name[:-1]
140-
results.append({"name": name,
141-
"path": post["path"],
142-
"size": post["size"]})
150+
name = rel_path
151+
else:
152+
name = rel_path
153+
# strip trailing slash if any
154+
if name.endswith("/"):
155+
name = name[:-1]
156+
# strip extension
157+
name = name[:-len(matched_ext)]
158+
results.append({"name": name,
159+
"path": post["path"],
160+
"size": post["size"]})
143161
except Exception as e:
144162
logging.error("读取页面目录 {} 错误: {},跳过".format(self.config["pages"]["path"][path_index], repr(e)))
145163
logging.info("读取页面列表成功")
@@ -153,17 +171,31 @@ def get_configs(self):
153171
self.config["configs"]["path"][path_index], self.config["configs"]["depth"][path_index],
154172
self.config["configs"].get("excludes"))
155173
for post in posts:
156-
flag = False
157-
for i in self.config["configs"]["type"]:
158-
if post["path"].endswith(i):
159-
flag = True
174+
if post["type"] != "file":
175+
continue
176+
matched_ext = None
177+
for ext in self.config["configs"]["type"]:
178+
if post["path"].endswith(ext):
179+
matched_ext = ext
160180
break
161-
if post["type"] == "file" and flag:
162-
name = post["path"][len(self.config["configs"]["path"][path_index]):]
163-
name = name[1:] if name[0] == "/" else name
164-
results.append({"name": name,
165-
"path": post["path"],
166-
"size": post["size"]})
181+
if not matched_ext:
182+
continue
183+
rel_path = post["path"]
184+
prefix = self.config["configs"]["path"][path_index]
185+
if prefix:
186+
prefix_slash = prefix if prefix.endswith("/") else prefix + "/"
187+
name = rel_path[len(prefix_slash):] if rel_path.startswith(prefix_slash) else rel_path
188+
else:
189+
name = rel_path
190+
name = name.rstrip("/")
191+
if len(matched_ext) < len(name):
192+
name_no_ext = name[:-len(matched_ext)]
193+
else:
194+
# Skip files where extension is not shorter than the name
195+
continue
196+
results.append({"name": name_no_ext,
197+
"path": post["path"],
198+
"size": post["size"]})
167199
except Exception as e:
168200
logging.error("读取配置 {} 错误: {},跳过".format(self.config["configs"]["path"][path_index], repr(e)))
169201
logging.info("读取博客配置列表成功")

hexoweb/libs/platforms/providers/gitEa.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,3 +131,47 @@ def create_hook(self, uri):
131131
self.request(url, "POST", data)
132132
logging.info("创建WebHook成功{}".format(data["config"]))
133133
return True
134+
135+
def get_tree(self, path, depth, exclude=None):
136+
"""
137+
Use Gitea API to fetch directory tree via git/trees endpoint.
138+
"""
139+
if not depth:
140+
return {"path": path, "data": []}
141+
if exclude is None:
142+
exclude = []
143+
# get branch SHA
144+
branch_info = self.request(f"/repos/{self.repo}/branches/{self.branch}", "GET").json()
145+
sha = branch_info.get("commit", {}).get("id")
146+
# fetch full tree recursively
147+
url = f"/repos/{self.repo}/git/trees/{sha}"
148+
tree_data = self.request(url, "GET", data={"recursive": "true"}).json().get("tree", [])
149+
prefix = (self.path + path).rstrip('/')
150+
results = []
151+
for element in tree_data:
152+
p = element.get("path")
153+
# filter by prefix
154+
if prefix:
155+
if not p.startswith(prefix + '/'):
156+
continue
157+
rel = p[len(prefix) + 1:]
158+
else:
159+
rel = p
160+
parts = rel.split('/') if rel else []
161+
# respect depth
162+
# if parts and len(parts) - len(path.split("/")) > depth: # no depth respect is a feature
163+
# continue
164+
# filter by exclude patterns
165+
if exclude and any(seg in exclude for seg in parts):
166+
continue
167+
name = parts[-1] if parts else ''
168+
typ = 'file' if element.get('type') == 'blob' else 'dir'
169+
item = {"name": name,
170+
"path": p[len(self.path):] if p.startswith(self.path) else p,
171+
"type": typ}
172+
if typ == 'file':
173+
size = element.get('size')
174+
if size is not None:
175+
item['size'] = size
176+
results.append(item)
177+
return results

0 commit comments

Comments
 (0)