mirror of
https://github.com/woodchen-ink/obsidian-publish-to-discourse.git
synced 2025-07-17 21:32:05 +08:00
Add internationalization (i18n) support
- Introduced i18n framework with translation support - Updated config, main, and styles files to use translation keys - Implemented dynamic locale setting based on Obsidian's language - Replaced hardcoded Chinese strings with translatable keys - Added locale change event listener to update translations dynamically
This commit is contained in:
parent
9d64d31947
commit
f27ada6a4e
@ -1,5 +1,6 @@
|
||||
import { PluginSettingTab, Setting, App } from 'obsidian';
|
||||
import DiscourseSyncPlugin from './main';
|
||||
import { t } from './i18n';
|
||||
|
||||
export interface DiscourseSyncSettings {
|
||||
baseUrl: string;
|
||||
@ -28,8 +29,8 @@ export class DiscourseSyncSettingsTab extends PluginSettingTab {
|
||||
containerEl.empty();
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("论坛地址")
|
||||
.setDesc("Discourse 论坛的网址")
|
||||
.setName(t('FORUM_URL'))
|
||||
.setDesc(t('FORUM_URL_DESC'))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("https://forum.example.com")
|
||||
@ -41,8 +42,8 @@ export class DiscourseSyncSettingsTab extends PluginSettingTab {
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("API 密钥")
|
||||
.setDesc("在'/admin/api/keys'中创建的 API 密钥")
|
||||
.setName(t('API_KEY'))
|
||||
.setDesc(t('API_KEY_DESC'))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("api_key")
|
||||
@ -54,8 +55,8 @@ export class DiscourseSyncSettingsTab extends PluginSettingTab {
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("用户名")
|
||||
.setDesc("Discourse 用户名")
|
||||
.setName(t('USERNAME'))
|
||||
.setDesc(t('USERNAME_DESC'))
|
||||
.addText((text) =>
|
||||
text
|
||||
.setPlaceholder("username")
|
||||
|
37
src/i18n/en.ts
Normal file
37
src/i18n/en.ts
Normal file
@ -0,0 +1,37 @@
|
||||
export default {
|
||||
// Settings page
|
||||
'FORUM_URL': 'Forum URL',
|
||||
'FORUM_URL_DESC': 'The URL of your Discourse forum',
|
||||
'API_KEY': 'API Key',
|
||||
'API_KEY_DESC': "API key created in '/admin/api/keys'",
|
||||
'USERNAME': 'Username',
|
||||
'USERNAME_DESC': 'Your Discourse username',
|
||||
|
||||
// Publish page
|
||||
'PUBLISH_TO_DISCOURSE': 'Publish to Discourse',
|
||||
'UPDATE_POST': 'Update Post',
|
||||
'CATEGORY': 'Category',
|
||||
'TAGS': 'Tags',
|
||||
'ENTER_TAG': 'Enter tag name (press Enter to add)',
|
||||
'ENTER_TAG_WITH_CREATE': 'Enter tag name (can create new tags)',
|
||||
'PUBLISHING': 'Publishing...',
|
||||
'UPDATING': 'Updating...',
|
||||
'PUBLISH': 'Publish',
|
||||
'UPDATE': 'Update',
|
||||
'RETRY': 'Retry',
|
||||
|
||||
// Success messages
|
||||
'PUBLISH_SUCCESS': '✓ Published successfully!',
|
||||
'UPDATE_SUCCESS': '✓ Updated successfully!',
|
||||
|
||||
// Error messages
|
||||
'PUBLISH_FAILED': 'Publish failed',
|
||||
'UPDATE_FAILED': 'Update failed',
|
||||
'PUBLISH_ERROR': 'Publish error',
|
||||
'UPDATE_ERROR': 'Update error',
|
||||
'PERMISSION_ERROR': 'Insufficient permissions, can only use existing tags',
|
||||
'UNKNOWN_ERROR': 'Unknown error',
|
||||
'TRY_AGAIN': 'Please try again',
|
||||
'POST_ID_ERROR': 'Published successfully but failed to get post ID',
|
||||
'SAVE_POST_ID_ERROR': 'Published successfully but failed to save post ID',
|
||||
}
|
30
src/i18n/index.ts
Normal file
30
src/i18n/index.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { moment } from 'obsidian';
|
||||
import zhCN from './zh-CN';
|
||||
import en from './en';
|
||||
|
||||
const localeMap: { [key: string]: any } = {
|
||||
'zh': zhCN,
|
||||
'zh-cn': zhCN,
|
||||
'en': en,
|
||||
'en-us': en,
|
||||
'en-gb': en,
|
||||
};
|
||||
|
||||
let currentLocale = 'en';
|
||||
|
||||
export function setLocale(locale: string) {
|
||||
const normalizedLocale = locale.toLowerCase();
|
||||
if (localeMap[normalizedLocale]) {
|
||||
currentLocale = normalizedLocale;
|
||||
moment.locale(normalizedLocale);
|
||||
}
|
||||
}
|
||||
|
||||
export function getCurrentLocale(): string {
|
||||
return currentLocale;
|
||||
}
|
||||
|
||||
export function t(key: string): string {
|
||||
const translations = localeMap[currentLocale] || localeMap['en'];
|
||||
return translations[key] || localeMap['en'][key] || key;
|
||||
}
|
37
src/i18n/zh-CN.ts
Normal file
37
src/i18n/zh-CN.ts
Normal file
@ -0,0 +1,37 @@
|
||||
export default {
|
||||
// 设置页面
|
||||
'FORUM_URL': '论坛地址',
|
||||
'FORUM_URL_DESC': 'Discourse 论坛的网址',
|
||||
'API_KEY': 'API 密钥',
|
||||
'API_KEY_DESC': "在'/admin/api/keys'中创建的 API 密钥",
|
||||
'USERNAME': '用户名',
|
||||
'USERNAME_DESC': 'Discourse 用户名',
|
||||
|
||||
// 发布页面
|
||||
'PUBLISH_TO_DISCOURSE': '发布到 Discourse',
|
||||
'UPDATE_POST': '更新帖子',
|
||||
'CATEGORY': '分类',
|
||||
'TAGS': '标签',
|
||||
'ENTER_TAG': '输入标签名称(回车添加)',
|
||||
'ENTER_TAG_WITH_CREATE': '输入标签名称(可创建新标签)',
|
||||
'PUBLISHING': '发布中...',
|
||||
'UPDATING': '更新中...',
|
||||
'PUBLISH': '发布',
|
||||
'UPDATE': '更新',
|
||||
'RETRY': '重试',
|
||||
|
||||
// 成功提示
|
||||
'PUBLISH_SUCCESS': '✓ 发布成功!',
|
||||
'UPDATE_SUCCESS': '✓ 更新成功!',
|
||||
|
||||
// 错误提示
|
||||
'PUBLISH_FAILED': '发布失败',
|
||||
'UPDATE_FAILED': '更新失败',
|
||||
'PUBLISH_ERROR': '发布出错',
|
||||
'UPDATE_ERROR': '更新出错',
|
||||
'PERMISSION_ERROR': '权限不足,只能使用已有的标签',
|
||||
'UNKNOWN_ERROR': '未知错误',
|
||||
'TRY_AGAIN': '请重试',
|
||||
'POST_ID_ERROR': '发布成功但无法获取帖子ID',
|
||||
'SAVE_POST_ID_ERROR': '发布成功但无法保存帖子ID',
|
||||
}
|
155
src/main.ts
155
src/main.ts
@ -1,15 +1,26 @@
|
||||
import { App, Menu, MenuItem, Plugin, Modal, requestUrl, TFile } from 'obsidian';
|
||||
import { App, Menu, MenuItem, Plugin, Modal, requestUrl, TFile, moment } from 'obsidian';
|
||||
import { DEFAULT_SETTINGS, DiscourseSyncSettings, DiscourseSyncSettingsTab } from './config';
|
||||
import * as yaml from 'yaml';
|
||||
import { t, setLocale } from './i18n';
|
||||
|
||||
export default class DiscourseSyncPlugin extends Plugin {
|
||||
settings: DiscourseSyncSettings;
|
||||
activeFile: {
|
||||
name: string;
|
||||
content: string;
|
||||
postId?: number; // 添加帖子ID字段
|
||||
postId?: number; // Post ID field
|
||||
};
|
||||
async onload() {
|
||||
// Set locale based on Obsidian's language setting
|
||||
setLocale(moment.locale());
|
||||
|
||||
// Update locale when Obsidian's language changes
|
||||
this.registerEvent(
|
||||
this.app.workspace.on('window-open', () => {
|
||||
setLocale(moment.locale());
|
||||
})
|
||||
);
|
||||
|
||||
await this.loadSettings();
|
||||
this.addSettingTab(new DiscourseSyncSettingsTab(this.app, this));
|
||||
this.registerEvent(
|
||||
@ -20,7 +31,7 @@ export default class DiscourseSyncPlugin extends Plugin {
|
||||
|
||||
this.addCommand({
|
||||
id: "category-modal",
|
||||
name: "发布到 Discourse",
|
||||
name: t('PUBLISH_TO_DISCOURSE'),
|
||||
callback: () => {
|
||||
this.openCategoryModal();
|
||||
},
|
||||
@ -118,11 +129,11 @@ export default class DiscourseSyncPlugin extends Plugin {
|
||||
}
|
||||
let content = this.activeFile.content;
|
||||
|
||||
// 检查是否包含帖子ID的frontmatter
|
||||
// Check if frontmatter contains post ID
|
||||
const frontMatter = this.getFrontMatter(content);
|
||||
const postId = frontMatter?.discourse_post_id;
|
||||
|
||||
// 过滤掉笔记属性部分
|
||||
// Filter out note properties section
|
||||
content = content.replace(/^---[\s\S]*?---\n/, '');
|
||||
|
||||
const imageReferences = this.extractImageReferences(content);
|
||||
@ -162,20 +173,20 @@ export default class DiscourseSyncPlugin extends Plugin {
|
||||
if (response.status === 200) {
|
||||
if (!isUpdate) {
|
||||
try {
|
||||
// 获取新帖子的ID
|
||||
// Get new post ID
|
||||
const responseData = response.json;
|
||||
if (responseData && responseData.id) {
|
||||
await this.updateFrontMatter(responseData.id);
|
||||
} else {
|
||||
return {
|
||||
message: "Error",
|
||||
error: "发布成功但无法获取帖子ID"
|
||||
error: t('POST_ID_ERROR')
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
message: "Error",
|
||||
error: "发布成功但无法保存帖子ID"
|
||||
error: t('SAVE_POST_ID_ERROR')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -198,20 +209,20 @@ export default class DiscourseSyncPlugin extends Plugin {
|
||||
} catch (parseError) {
|
||||
return {
|
||||
message: "Error",
|
||||
error: `${isUpdate ? '更新' : '发布'}失败 (${response.status})`
|
||||
error: `${isUpdate ? t('UPDATE_FAILED') : t('PUBLISH_FAILED')} (${response.status})`
|
||||
};
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
message: "Error",
|
||||
error: `${isUpdate ? '更新' : '发布'}失败: ${error.message || '未知错误'}`
|
||||
error: `${isUpdate ? t('UPDATE_FAILED') : t('PUBLISH_FAILED')}: ${error.message || t('UNKNOWN_ERROR')}`
|
||||
};
|
||||
}
|
||||
return { message: "Error", error: `${isUpdate ? '更新' : '发布'}失败,请重试` };
|
||||
return { message: "Error", error: `${isUpdate ? t('UPDATE_FAILED') : t('PUBLISH_FAILED')}, ${t('TRY_AGAIN')}` };
|
||||
}
|
||||
|
||||
// 获取frontmatter数据
|
||||
// Get frontmatter data
|
||||
private getFrontMatter(content: string): any {
|
||||
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
||||
if (fmMatch) {
|
||||
@ -224,10 +235,10 @@ export default class DiscourseSyncPlugin extends Plugin {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 更新frontmatter,添加帖子ID
|
||||
// Update frontmatter, add post ID
|
||||
private async updateFrontMatter(postId: number) {
|
||||
try {
|
||||
// 获取当前活动文件
|
||||
// Get current active file
|
||||
const activeFile = this.app.workspace.getActiveFile();
|
||||
if (!activeFile) {
|
||||
return;
|
||||
@ -238,11 +249,11 @@ export default class DiscourseSyncPlugin extends Plugin {
|
||||
|
||||
let newContent: string;
|
||||
if (fm) {
|
||||
// 更新现有的frontmatter
|
||||
// Update existing frontmatter
|
||||
const updatedFm = { ...fm, discourse_post_id: postId };
|
||||
newContent = content.replace(/^---\n[\s\S]*?\n---\n/, `---\n${yaml.stringify(updatedFm)}---\n`);
|
||||
} else {
|
||||
// 添加新的frontmatter
|
||||
// Add new frontmatter
|
||||
const newFm = { discourse_post_id: postId };
|
||||
newContent = `---\n${yaml.stringify(newFm)}---\n${content}`;
|
||||
}
|
||||
@ -251,7 +262,7 @@ export default class DiscourseSyncPlugin extends Plugin {
|
||||
} catch (error) {
|
||||
return {
|
||||
message: "Error",
|
||||
error: "更新失败"
|
||||
error: t('UPDATE_FAILED')
|
||||
};
|
||||
}
|
||||
}
|
||||
@ -309,9 +320,9 @@ export default class DiscourseSyncPlugin extends Plugin {
|
||||
|
||||
if (response.status === 200) {
|
||||
const data = response.json;
|
||||
// 获取用户权限信息
|
||||
// Get user permissions
|
||||
const canCreateTags = await this.checkCanCreateTags();
|
||||
// Discourse 返回的 tags 列表在 tags 数组中
|
||||
// Tags list returned by Discourse is in the tags array
|
||||
return data.tags.map((tag: any) => ({
|
||||
name: tag.name,
|
||||
canCreate: canCreateTags
|
||||
@ -323,7 +334,7 @@ export default class DiscourseSyncPlugin extends Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
// 检查用户是否有创建标签的权限
|
||||
// Check if user has permission to create tags
|
||||
private async checkCanCreateTags(): Promise<boolean> {
|
||||
try {
|
||||
const url = `${this.settings.baseUrl}/u/${this.settings.disUser}.json`;
|
||||
@ -342,7 +353,7 @@ export default class DiscourseSyncPlugin extends Plugin {
|
||||
|
||||
if (response.status === 200) {
|
||||
const data = response.json;
|
||||
// 检查用户的 trust_level
|
||||
// Check user's trust_level
|
||||
return data.user.trust_level >= 3;
|
||||
}
|
||||
return false;
|
||||
@ -353,7 +364,7 @@ export default class DiscourseSyncPlugin extends Plugin {
|
||||
|
||||
registerDirMenu(menu: Menu, file: TFile) {
|
||||
const syncDiscourse = (item: MenuItem) => {
|
||||
item.setTitle("发布到 Discourse");
|
||||
item.setTitle(t('PUBLISH_TO_DISCOURSE'));
|
||||
item.onClick(async () => {
|
||||
const content = await this.app.vault.read(file);
|
||||
const fm = this.getFrontMatter(content);
|
||||
@ -441,23 +452,23 @@ export class SelectCategoryModal extends Modal {
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
// 添加模态框基础样式
|
||||
// Add modal base style
|
||||
this.modalEl.addClass('mod-discourse-sync');
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
contentEl.addClass('discourse-sync-modal');
|
||||
|
||||
const isUpdate = this.plugin.activeFile.postId !== undefined;
|
||||
contentEl.createEl("h1", { text: isUpdate ? '更新帖子' : '发布到 Discourse' });
|
||||
contentEl.createEl("h1", { text: isUpdate ? t('UPDATE_POST') : t('PUBLISH_TO_DISCOURSE') });
|
||||
|
||||
// 创建表单区域容器
|
||||
// Create form area container
|
||||
const formArea = contentEl.createEl('div', { cls: 'form-area' });
|
||||
|
||||
// 创建选择器容器
|
||||
// Create selector container
|
||||
const selectContainer = formArea.createEl('div', { cls: 'select-container' });
|
||||
if (!isUpdate) {
|
||||
// 只在新建帖子时显示分类选择
|
||||
selectContainer.createEl('label', { text: '分类' });
|
||||
// Only show category selector when creating a new post
|
||||
selectContainer.createEl('label', { text: t('CATEGORY') });
|
||||
const selectEl = selectContainer.createEl('select');
|
||||
this.categories.forEach(category => {
|
||||
const option = selectEl.createEl('option', { text: category.name });
|
||||
@ -465,18 +476,18 @@ export class SelectCategoryModal extends Modal {
|
||||
});
|
||||
}
|
||||
|
||||
// 创建标签选择容器
|
||||
// Create tag selector container
|
||||
const tagContainer = formArea.createEl('div', { cls: 'tag-container' });
|
||||
tagContainer.createEl('label', { text: '标签' });
|
||||
tagContainer.createEl('label', { text: t('TAGS') });
|
||||
|
||||
// 创建标签选择区域
|
||||
// Create tag selector area
|
||||
const tagSelectArea = tagContainer.createEl('div', { cls: 'tag-select-area' });
|
||||
|
||||
// 已选标签显示区域
|
||||
// Selected tags display area
|
||||
const selectedTagsContainer = tagSelectArea.createEl('div', { cls: 'selected-tags' });
|
||||
const selectedTags = new Set<string>();
|
||||
|
||||
// 更新已选标签显示
|
||||
// Update selected tags display
|
||||
const updateSelectedTags = () => {
|
||||
selectedTagsContainer.empty();
|
||||
selectedTags.forEach(tag => {
|
||||
@ -495,19 +506,19 @@ export class SelectCategoryModal extends Modal {
|
||||
});
|
||||
};
|
||||
|
||||
// 创建标签输入容器
|
||||
// Create tag input container
|
||||
const tagInputContainer = tagSelectArea.createEl('div', { cls: 'tag-input-container' });
|
||||
|
||||
// 创建标签输入和建议
|
||||
// Create tag input and suggestions
|
||||
const tagInput = tagInputContainer.createEl('input', {
|
||||
type: 'text',
|
||||
placeholder: this.canCreateTags ? '输入标签名称(回车添加)' : '输入标签名称(回车添加)'
|
||||
placeholder: this.canCreateTags ? t('ENTER_TAG_WITH_CREATE') : t('ENTER_TAG')
|
||||
});
|
||||
|
||||
// 创建标签建议容器
|
||||
// Create tag suggestions container
|
||||
const tagSuggestions = tagInputContainer.createEl('div', { cls: 'tag-suggestions' });
|
||||
|
||||
// 处理输入事件,显示匹配的标签
|
||||
// Handle input event, show matching tags
|
||||
tagInput.oninput = () => {
|
||||
const value = tagInput.value.toLowerCase();
|
||||
tagSuggestions.empty();
|
||||
@ -521,14 +532,14 @@ export class SelectCategoryModal extends Modal {
|
||||
.slice(0, 10);
|
||||
|
||||
if (matches.length > 0) {
|
||||
// 获取输入框的位置和宽度
|
||||
// Get input box position and width
|
||||
const inputRect = tagInput.getBoundingClientRect();
|
||||
const modalRect = this.modalEl.getBoundingClientRect();
|
||||
|
||||
// 确保建议列表不会超出模态框宽度
|
||||
const maxWidth = modalRect.right - inputRect.left - 24; // 24px 是右侧padding
|
||||
// Ensure suggestions list doesn't exceed modal width
|
||||
const maxWidth = modalRect.right - inputRect.left - 24; // 24px is right padding
|
||||
|
||||
// 设置建议列表的位置和宽度
|
||||
// Set suggestions list position and width
|
||||
tagSuggestions.style.top = `${inputRect.bottom + 4}px`;
|
||||
tagSuggestions.style.left = `${inputRect.left}px`;
|
||||
tagSuggestions.style.width = `${Math.min(inputRect.width, maxWidth)}px`;
|
||||
@ -549,7 +560,7 @@ export class SelectCategoryModal extends Modal {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理回车事件
|
||||
// Handle enter event
|
||||
tagInput.onkeydown = (e) => {
|
||||
if (e.key === 'Enter' && tagInput.value) {
|
||||
e.preventDefault();
|
||||
@ -563,10 +574,10 @@ export class SelectCategoryModal extends Modal {
|
||||
selectedTags.add(value);
|
||||
updateSelectedTags();
|
||||
} else {
|
||||
// 显示权限提示
|
||||
// Show permission notice
|
||||
const notice = contentEl.createEl('div', {
|
||||
cls: 'tag-notice',
|
||||
text: '权限不足,只能使用已有的标签'
|
||||
text: t('PERMISSION_ERROR')
|
||||
});
|
||||
setTimeout(() => {
|
||||
notice.remove();
|
||||
@ -578,15 +589,15 @@ export class SelectCategoryModal extends Modal {
|
||||
}
|
||||
};
|
||||
|
||||
// 处理失焦事件,隐藏建议
|
||||
// Handle blur event, hide suggestions
|
||||
tagInput.onblur = () => {
|
||||
// 延迟隐藏,以便可以点击建议
|
||||
// Delay hide, so suggestions can be clicked
|
||||
setTimeout(() => {
|
||||
tagSuggestions.empty();
|
||||
}, 200);
|
||||
};
|
||||
|
||||
// 处理窗口滚动时更新建议列表位置
|
||||
// Handle window scroll, update suggestions list position
|
||||
const updateSuggestionsPosition = () => {
|
||||
if (tagSuggestions.childNodes.length > 0) {
|
||||
const inputRect = tagInput.getBoundingClientRect();
|
||||
@ -596,22 +607,22 @@ export class SelectCategoryModal extends Modal {
|
||||
}
|
||||
};
|
||||
|
||||
// 监听滚动事件
|
||||
// Listen for scroll event
|
||||
this.modalEl.addEventListener('scroll', updateSuggestionsPosition);
|
||||
|
||||
// 在模态框关闭时移除事件监听
|
||||
// Remove event listeners when modal closes
|
||||
this.modalEl.onclose = () => {
|
||||
this.modalEl.removeEventListener('scroll', updateSuggestionsPosition);
|
||||
};
|
||||
|
||||
// 创建按钮区域
|
||||
// Create button area
|
||||
const buttonArea = contentEl.createEl('div', { cls: 'button-area' });
|
||||
const submitButton = buttonArea.createEl('button', {
|
||||
text: isUpdate ? '更新' : '发布',
|
||||
text: isUpdate ? t('UPDATE') : t('PUBLISH'),
|
||||
cls: 'submit-button'
|
||||
});
|
||||
|
||||
// 创建提示信息容器
|
||||
// Create notice container
|
||||
const noticeContainer = buttonArea.createEl('div');
|
||||
|
||||
submitButton.onclick = async () => {
|
||||
@ -625,25 +636,25 @@ export class SelectCategoryModal extends Modal {
|
||||
await this.plugin.saveSettings();
|
||||
}
|
||||
|
||||
// 保存选中的标签
|
||||
// Save selected tags
|
||||
this.plugin.settings.selectedTags = Array.from(selectedTags);
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
// 禁用提交按钮,显示加载状态
|
||||
// Disable submit button, show loading state
|
||||
submitButton.disabled = true;
|
||||
submitButton.textContent = isUpdate ? '更新中...' : '发布中...';
|
||||
submitButton.textContent = isUpdate ? t('UPDATING') : t('PUBLISHING');
|
||||
|
||||
try {
|
||||
const reply = await this.plugin.postTopic();
|
||||
|
||||
// 显示提示信息
|
||||
// Show notice
|
||||
noticeContainer.empty();
|
||||
if (reply.message === 'Success') {
|
||||
noticeContainer.createEl('div', {
|
||||
cls: 'notice success',
|
||||
text: isUpdate ? '✓ 更新成功!' : '✓ 发布成功!'
|
||||
text: isUpdate ? t('UPDATE_SUCCESS') : t('PUBLISH_SUCCESS')
|
||||
});
|
||||
// 成功后延迟关闭
|
||||
// Close after success
|
||||
setTimeout(() => {
|
||||
this.close();
|
||||
}, 1500);
|
||||
@ -651,24 +662,24 @@ export class SelectCategoryModal extends Modal {
|
||||
const errorContainer = noticeContainer.createEl('div', { cls: 'notice error' });
|
||||
errorContainer.createEl('div', {
|
||||
cls: 'error-title',
|
||||
text: isUpdate ? '更新失败' : '发布失败'
|
||||
text: isUpdate ? t('UPDATE_ERROR') : t('PUBLISH_ERROR')
|
||||
});
|
||||
|
||||
// 显示 Discourse 返回的具体错误信息
|
||||
// Show Discourse-specific error information
|
||||
errorContainer.createEl('div', {
|
||||
cls: 'error-message',
|
||||
text: reply.error || (isUpdate ? '更新失败,请重试' : '发布失败,请重试')
|
||||
text: reply.error || (isUpdate ? t('UPDATE_FAILED') + ', ' + t('TRY_AGAIN') : t('PUBLISH_FAILED') + ', ' + t('TRY_AGAIN'))
|
||||
});
|
||||
|
||||
// 添加重试按钮
|
||||
// Add retry button
|
||||
const retryButton = errorContainer.createEl('button', {
|
||||
cls: 'retry-button',
|
||||
text: '重试'
|
||||
text: t('RETRY')
|
||||
});
|
||||
retryButton.onclick = () => {
|
||||
noticeContainer.empty();
|
||||
submitButton.disabled = false;
|
||||
submitButton.textContent = isUpdate ? '更新' : '发布';
|
||||
submitButton.textContent = isUpdate ? t('UPDATE') : t('PUBLISH');
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
@ -676,29 +687,29 @@ export class SelectCategoryModal extends Modal {
|
||||
const errorContainer = noticeContainer.createEl('div', { cls: 'notice error' });
|
||||
errorContainer.createEl('div', {
|
||||
cls: 'error-title',
|
||||
text: isUpdate ? '更新出错' : '发布出错'
|
||||
text: isUpdate ? t('UPDATE_ERROR') : t('PUBLISH_ERROR')
|
||||
});
|
||||
errorContainer.createEl('div', {
|
||||
cls: 'error-message',
|
||||
text: error.message || '未知错误'
|
||||
text: error.message || t('UNKNOWN_ERROR')
|
||||
});
|
||||
|
||||
// 添加重试按钮
|
||||
// Add retry button
|
||||
const retryButton = errorContainer.createEl('button', {
|
||||
cls: 'retry-button',
|
||||
text: '重试'
|
||||
text: t('RETRY')
|
||||
});
|
||||
retryButton.onclick = () => {
|
||||
noticeContainer.empty();
|
||||
submitButton.disabled = false;
|
||||
submitButton.textContent = isUpdate ? '更新' : '发布';
|
||||
submitButton.textContent = isUpdate ? t('UPDATE') : t('PUBLISH');
|
||||
};
|
||||
}
|
||||
|
||||
// 如果发生错误,重置按钮状态
|
||||
// If error occurs, reset button state
|
||||
if (submitButton.disabled) {
|
||||
submitButton.disabled = false;
|
||||
submitButton.textContent = isUpdate ? '更新' : '发布';
|
||||
submitButton.textContent = isUpdate ? t('UPDATE') : t('PUBLISH');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
36
styles.css
36
styles.css
@ -7,7 +7,7 @@ If your plugin does not need CSS, delete this file.
|
||||
|
||||
*/
|
||||
|
||||
/* 基础模态框样式覆盖 */
|
||||
/* Basic Modal Style Override */
|
||||
.modal.mod-discourse-sync {
|
||||
max-width: 500px;
|
||||
max-height: 90vh;
|
||||
@ -18,7 +18,7 @@ If your plugin does not need CSS, delete this file.
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
/* 内容区域样式 */
|
||||
/* Content Area Style */
|
||||
.discourse-sync-modal {
|
||||
padding: 24px;
|
||||
width: 100%;
|
||||
@ -28,14 +28,14 @@ If your plugin does not need CSS, delete this file.
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
/* 表单区域 */
|
||||
/* Form Area */
|
||||
.discourse-sync-modal .form-area {
|
||||
flex-grow: 1;
|
||||
min-height: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
/* 按钮区域固定在底部 */
|
||||
/* Button Area Fixed at Bottom */
|
||||
.discourse-sync-modal .button-area {
|
||||
flex-shrink: 0;
|
||||
margin-top: auto;
|
||||
@ -48,11 +48,11 @@ If your plugin does not need CSS, delete this file.
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
/* 表单容器通用样式 */
|
||||
/* Common Form Container Style */
|
||||
.discourse-sync-modal .select-container,
|
||||
.discourse-sync-modal .tag-container {
|
||||
margin-bottom: 24px;
|
||||
padding: 0; /* 移除内边距 */
|
||||
padding: 0; /* Remove padding */
|
||||
}
|
||||
|
||||
.discourse-sync-modal .select-container label,
|
||||
@ -63,7 +63,7 @@ If your plugin does not need CSS, delete this file.
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
/* 输入框和选择器样式 */
|
||||
/* Input and Selector Style */
|
||||
.discourse-sync-modal select,
|
||||
.discourse-sync-modal .tag-select-area {
|
||||
width: 100%;
|
||||
@ -108,7 +108,7 @@ If your plugin does not need CSS, delete this file.
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* 提示信息样式 */
|
||||
/* Notice Style */
|
||||
.discourse-sync-modal .notice {
|
||||
margin-top: 16px;
|
||||
padding: 16px;
|
||||
@ -171,7 +171,7 @@ If your plugin does not need CSS, delete this file.
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* 标签选择区域样式 */
|
||||
/* Tag Select Area Style */
|
||||
.discourse-sync-modal .tag-select-area {
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
@ -181,7 +181,7 @@ If your plugin does not need CSS, delete this file.
|
||||
}
|
||||
|
||||
.discourse-sync-modal .selected-tags {
|
||||
display: none; /* 默认隐藏 */
|
||||
display: none; /* Hidden by default */
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-bottom: 8px;
|
||||
@ -189,7 +189,7 @@ If your plugin does not need CSS, delete this file.
|
||||
}
|
||||
|
||||
.discourse-sync-modal .selected-tags:not(:empty) {
|
||||
display: flex; /* 当有内容时显示 */
|
||||
display: flex; /* Show when has content */
|
||||
}
|
||||
|
||||
.discourse-sync-modal .tag {
|
||||
@ -237,13 +237,13 @@ If your plugin does not need CSS, delete this file.
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
/* 标签输入容器 */
|
||||
/* Tag Input Container */
|
||||
.discourse-sync-modal .tag-input-container {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* 标签建议下拉框 */
|
||||
/* Tag Suggestion Dropdown */
|
||||
.discourse-sync-modal .tag-suggestions {
|
||||
position: fixed;
|
||||
background-color: var(--background-primary);
|
||||
@ -254,14 +254,14 @@ If your plugin does not need CSS, delete this file.
|
||||
max-height: 180px;
|
||||
overflow-y: auto;
|
||||
margin-top: 4px;
|
||||
display: none; /* 默认隐藏 */
|
||||
display: none; /* Hidden by default */
|
||||
}
|
||||
|
||||
.discourse-sync-modal .tag-suggestions:not(:empty) {
|
||||
display: block; /* 当有内容时显示 */
|
||||
display: block; /* Show when has content */
|
||||
}
|
||||
|
||||
/* 标签建议项 */
|
||||
/* Tag Suggestion Item */
|
||||
.discourse-sync-modal .tag-suggestion {
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
@ -279,7 +279,7 @@ If your plugin does not need CSS, delete this file.
|
||||
background-color: var(--background-modifier-hover);
|
||||
}
|
||||
|
||||
/* 美化滚动条 */
|
||||
/* Tag Suggestion Scrollbar Style */
|
||||
.discourse-sync-modal .tag-suggestions::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
@ -297,7 +297,7 @@ If your plugin does not need CSS, delete this file.
|
||||
background-color: var(--background-modifier-border-hover);
|
||||
}
|
||||
|
||||
/* 移除 tag-container 的 z-index,避免干扰 */
|
||||
/* Remove tag-container z-index to avoid interference */
|
||||
.discourse-sync-modal .tag-container {
|
||||
position: relative;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user