mirror of
https://github.com/woodchen-ink/obsidian-publish-to-discourse.git
synced 2025-07-18 05:42:05 +08:00
Enhance DiscourseSyncPlugin with tag management features
- Updated styles for modal and form areas to improve layout and usability. - Added support for selecting and managing tags in the DiscourseSync settings. - Implemented fetching of available tags from the Discourse API. - Enhanced the category selection modal to include tag selection functionality. - Updated settings interface to store selected tags. - Improved error handling and user feedback during tag operations.
This commit is contained in:
parent
efbb0a5c9c
commit
ce2d944ee6
@ -6,6 +6,7 @@ export interface DiscourseSyncSettings {
|
|||||||
apiKey: string;
|
apiKey: string;
|
||||||
disUser: string;
|
disUser: string;
|
||||||
category: number;
|
category: number;
|
||||||
|
selectedTags: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export const DEFAULT_SETTINGS: DiscourseSyncSettings = {
|
export const DEFAULT_SETTINGS: DiscourseSyncSettings = {
|
||||||
@ -13,6 +14,7 @@ export const DEFAULT_SETTINGS: DiscourseSyncSettings = {
|
|||||||
apiKey: "apikey",
|
apiKey: "apikey",
|
||||||
disUser: "DiscourseUsername",
|
disUser: "DiscourseUsername",
|
||||||
category: 1,
|
category: 1,
|
||||||
|
selectedTags: []
|
||||||
};
|
};
|
||||||
|
|
||||||
export class DiscourseSyncSettingsTab extends PluginSettingTab {
|
export class DiscourseSyncSettingsTab extends PluginSettingTab {
|
||||||
|
241
src/main.ts
241
src/main.ts
@ -140,11 +140,13 @@ export default class DiscourseSyncPlugin extends Plugin {
|
|||||||
|
|
||||||
const body = JSON.stringify(isUpdate ? {
|
const body = JSON.stringify(isUpdate ? {
|
||||||
raw: content,
|
raw: content,
|
||||||
edit_reason: "Updated from Obsidian"
|
edit_reason: "Updated from Obsidian",
|
||||||
|
tags: this.settings.selectedTags || []
|
||||||
} : {
|
} : {
|
||||||
title: this.activeFile.name,
|
title: this.activeFile.name,
|
||||||
raw: content,
|
raw: content,
|
||||||
category: this.settings.category
|
category: this.settings.category,
|
||||||
|
tags: this.settings.selectedTags || []
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -228,7 +230,6 @@ export default class DiscourseSyncPlugin extends Plugin {
|
|||||||
// 获取当前活动文件
|
// 获取当前活动文件
|
||||||
const activeFile = this.app.workspace.getActiveFile();
|
const activeFile = this.app.workspace.getActiveFile();
|
||||||
if (!activeFile) {
|
if (!activeFile) {
|
||||||
console.error('No active file found');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,9 +248,11 @@ export default class DiscourseSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.app.vault.modify(activeFile, newContent);
|
await this.app.vault.modify(activeFile, newContent);
|
||||||
console.log('Successfully updated frontmatter with post ID:', postId);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error updating frontmatter:', error);
|
return {
|
||||||
|
message: "Error",
|
||||||
|
error: "更新失败"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,6 +291,66 @@ export default class DiscourseSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async fetchTags(): Promise<{ name: string; canCreate: boolean }[]> {
|
||||||
|
const url = `${this.settings.baseUrl}/tags.json`;
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Api-Key": this.settings.apiKey,
|
||||||
|
"Api-Username": this.settings.disUser,
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await requestUrl({
|
||||||
|
url: url,
|
||||||
|
method: "GET",
|
||||||
|
contentType: "application/json",
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
const data = response.json;
|
||||||
|
// 获取用户权限信息
|
||||||
|
const canCreateTags = await this.checkCanCreateTags();
|
||||||
|
// Discourse 返回的 tags 列表在 tags 数组中
|
||||||
|
return data.tags.map((tag: any) => ({
|
||||||
|
name: tag.name,
|
||||||
|
canCreate: canCreateTags
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
} catch (error) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户是否有创建标签的权限
|
||||||
|
private async checkCanCreateTags(): Promise<boolean> {
|
||||||
|
try {
|
||||||
|
const url = `${this.settings.baseUrl}/u/${this.settings.disUser}.json`;
|
||||||
|
const headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Api-Key": this.settings.apiKey,
|
||||||
|
"Api-Username": this.settings.disUser,
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await requestUrl({
|
||||||
|
url: url,
|
||||||
|
method: "GET",
|
||||||
|
contentType: "application/json",
|
||||||
|
headers,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
const data = response.json;
|
||||||
|
// 检查用户的 trust_level
|
||||||
|
return data.user.trust_level >= 3;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
registerDirMenu(menu: Menu, file: TFile) {
|
registerDirMenu(menu: Menu, file: TFile) {
|
||||||
const syncDiscourse = (item: MenuItem) => {
|
const syncDiscourse = (item: MenuItem) => {
|
||||||
item.setTitle("发布到 Discourse");
|
item.setTitle("发布到 Discourse");
|
||||||
@ -306,9 +369,12 @@ export default class DiscourseSyncPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async openCategoryModal() {
|
private async openCategoryModal() {
|
||||||
const categories = await this.fetchCategories();
|
const [categories, tags] = await Promise.all([
|
||||||
|
this.fetchCategories(),
|
||||||
|
this.fetchTags()
|
||||||
|
]);
|
||||||
if (categories.length > 0) {
|
if (categories.length > 0) {
|
||||||
new SelectCategoryModal(this.app, this, categories).open();
|
new SelectCategoryModal(this.app, this, categories, tags).open();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -363,10 +429,15 @@ export class NotifyUser extends Modal {
|
|||||||
export class SelectCategoryModal extends Modal {
|
export class SelectCategoryModal extends Modal {
|
||||||
plugin: DiscourseSyncPlugin;
|
plugin: DiscourseSyncPlugin;
|
||||||
categories: {id: number; name: string}[];
|
categories: {id: number; name: string}[];
|
||||||
constructor(app: App, plugin: DiscourseSyncPlugin, categories: {id: number; name: string }[]) {
|
tags: { name: string; canCreate: boolean }[];
|
||||||
|
canCreateTags = false;
|
||||||
|
|
||||||
|
constructor(app: App, plugin: DiscourseSyncPlugin, categories: {id: number; name: string }[], tags: { name: string; canCreate: boolean }[]) {
|
||||||
super(app);
|
super(app);
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.categories = categories;
|
this.categories = categories;
|
||||||
|
this.tags = tags;
|
||||||
|
this.canCreateTags = tags.length > 0 ? tags[0].canCreate : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpen() {
|
onOpen() {
|
||||||
@ -379,8 +450,11 @@ export class SelectCategoryModal extends Modal {
|
|||||||
const isUpdate = this.plugin.activeFile.postId !== undefined;
|
const isUpdate = this.plugin.activeFile.postId !== undefined;
|
||||||
contentEl.createEl("h1", { text: isUpdate ? '更新帖子' : '发布到 Discourse' });
|
contentEl.createEl("h1", { text: isUpdate ? '更新帖子' : '发布到 Discourse' });
|
||||||
|
|
||||||
|
// 创建表单区域容器
|
||||||
|
const formArea = contentEl.createEl('div', { cls: 'form-area' });
|
||||||
|
|
||||||
// 创建选择器容器
|
// 创建选择器容器
|
||||||
const selectContainer = contentEl.createEl('div', { cls: 'select-container' });
|
const selectContainer = formArea.createEl('div', { cls: 'select-container' });
|
||||||
if (!isUpdate) {
|
if (!isUpdate) {
|
||||||
// 只在新建帖子时显示分类选择
|
// 只在新建帖子时显示分类选择
|
||||||
selectContainer.createEl('label', { text: '分类' });
|
selectContainer.createEl('label', { text: '分类' });
|
||||||
@ -391,13 +465,154 @@ export class SelectCategoryModal extends Modal {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const submitButton = contentEl.createEl('button', {
|
// 创建标签选择容器
|
||||||
|
const tagContainer = formArea.createEl('div', { cls: 'tag-container' });
|
||||||
|
tagContainer.createEl('label', { text: '标签' });
|
||||||
|
|
||||||
|
// 创建标签选择区域
|
||||||
|
const tagSelectArea = tagContainer.createEl('div', { cls: 'tag-select-area' });
|
||||||
|
|
||||||
|
// 已选标签显示区域
|
||||||
|
const selectedTagsContainer = tagSelectArea.createEl('div', { cls: 'selected-tags' });
|
||||||
|
const selectedTags = new Set<string>();
|
||||||
|
|
||||||
|
// 更新已选标签显示
|
||||||
|
const updateSelectedTags = () => {
|
||||||
|
selectedTagsContainer.empty();
|
||||||
|
selectedTags.forEach(tag => {
|
||||||
|
const tagEl = selectedTagsContainer.createEl('span', {
|
||||||
|
cls: 'tag',
|
||||||
|
text: tag
|
||||||
|
});
|
||||||
|
const removeBtn = tagEl.createEl('span', {
|
||||||
|
cls: 'remove-tag',
|
||||||
|
text: '×'
|
||||||
|
});
|
||||||
|
removeBtn.onclick = () => {
|
||||||
|
selectedTags.delete(tag);
|
||||||
|
updateSelectedTags();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建标签输入容器
|
||||||
|
const tagInputContainer = tagSelectArea.createEl('div', { cls: 'tag-input-container' });
|
||||||
|
|
||||||
|
// 创建标签输入和建议
|
||||||
|
const tagInput = tagInputContainer.createEl('input', {
|
||||||
|
type: 'text',
|
||||||
|
placeholder: this.canCreateTags ? '输入标签名称(回车添加)' : '输入标签名称(回车添加)'
|
||||||
|
});
|
||||||
|
|
||||||
|
// 创建标签建议容器
|
||||||
|
const tagSuggestions = tagInputContainer.createEl('div', { cls: 'tag-suggestions' });
|
||||||
|
|
||||||
|
// 处理输入事件,显示匹配的标签
|
||||||
|
tagInput.oninput = () => {
|
||||||
|
const value = tagInput.value.toLowerCase();
|
||||||
|
tagSuggestions.empty();
|
||||||
|
|
||||||
|
if (value) {
|
||||||
|
const matches = this.tags
|
||||||
|
.filter(tag =>
|
||||||
|
tag.name.toLowerCase().includes(value) &&
|
||||||
|
!selectedTags.has(tag.name)
|
||||||
|
)
|
||||||
|
.slice(0, 10);
|
||||||
|
|
||||||
|
if (matches.length > 0) {
|
||||||
|
// 获取输入框的位置和宽度
|
||||||
|
const inputRect = tagInput.getBoundingClientRect();
|
||||||
|
const modalRect = this.modalEl.getBoundingClientRect();
|
||||||
|
|
||||||
|
// 确保建议列表不会超出模态框宽度
|
||||||
|
const maxWidth = modalRect.right - inputRect.left - 24; // 24px 是右侧padding
|
||||||
|
|
||||||
|
// 设置建议列表的位置和宽度
|
||||||
|
tagSuggestions.style.top = `${inputRect.bottom + 4}px`;
|
||||||
|
tagSuggestions.style.left = `${inputRect.left}px`;
|
||||||
|
tagSuggestions.style.width = `${Math.min(inputRect.width, maxWidth)}px`;
|
||||||
|
|
||||||
|
matches.forEach(tag => {
|
||||||
|
const suggestion = tagSuggestions.createEl('div', {
|
||||||
|
cls: 'tag-suggestion',
|
||||||
|
text: tag.name
|
||||||
|
});
|
||||||
|
suggestion.onclick = () => {
|
||||||
|
selectedTags.add(tag.name);
|
||||||
|
tagInput.value = '';
|
||||||
|
tagSuggestions.empty();
|
||||||
|
updateSelectedTags();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理回车事件
|
||||||
|
tagInput.onkeydown = (e) => {
|
||||||
|
if (e.key === 'Enter' && tagInput.value) {
|
||||||
|
e.preventDefault();
|
||||||
|
const value = tagInput.value.trim();
|
||||||
|
if (value && !selectedTags.has(value)) {
|
||||||
|
const existingTag = this.tags.find(t => t.name.toLowerCase() === value.toLowerCase());
|
||||||
|
if (existingTag) {
|
||||||
|
selectedTags.add(existingTag.name);
|
||||||
|
updateSelectedTags();
|
||||||
|
} else if (this.canCreateTags) {
|
||||||
|
selectedTags.add(value);
|
||||||
|
updateSelectedTags();
|
||||||
|
} else {
|
||||||
|
// 显示权限提示
|
||||||
|
const notice = contentEl.createEl('div', {
|
||||||
|
cls: 'tag-notice',
|
||||||
|
text: '权限不足,只能使用已有的标签'
|
||||||
|
});
|
||||||
|
setTimeout(() => {
|
||||||
|
notice.remove();
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tagInput.value = '';
|
||||||
|
tagSuggestions.empty();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理失焦事件,隐藏建议
|
||||||
|
tagInput.onblur = () => {
|
||||||
|
// 延迟隐藏,以便可以点击建议
|
||||||
|
setTimeout(() => {
|
||||||
|
tagSuggestions.empty();
|
||||||
|
}, 200);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理窗口滚动时更新建议列表位置
|
||||||
|
const updateSuggestionsPosition = () => {
|
||||||
|
if (tagSuggestions.childNodes.length > 0) {
|
||||||
|
const inputRect = tagInput.getBoundingClientRect();
|
||||||
|
tagSuggestions.style.top = `${inputRect.bottom + 4}px`;
|
||||||
|
tagSuggestions.style.left = `${inputRect.left}px`;
|
||||||
|
tagSuggestions.style.width = `${inputRect.width}px`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 监听滚动事件
|
||||||
|
this.modalEl.addEventListener('scroll', updateSuggestionsPosition);
|
||||||
|
|
||||||
|
// 在模态框关闭时移除事件监听
|
||||||
|
this.modalEl.onclose = () => {
|
||||||
|
this.modalEl.removeEventListener('scroll', updateSuggestionsPosition);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建按钮区域
|
||||||
|
const buttonArea = contentEl.createEl('div', { cls: 'button-area' });
|
||||||
|
const submitButton = buttonArea.createEl('button', {
|
||||||
text: isUpdate ? '更新' : '发布',
|
text: isUpdate ? '更新' : '发布',
|
||||||
cls: 'submit-button'
|
cls: 'submit-button'
|
||||||
});
|
});
|
||||||
|
|
||||||
// 创建提示信息容器
|
// 创建提示信息容器
|
||||||
const noticeContainer = contentEl.createEl('div');
|
const noticeContainer = buttonArea.createEl('div');
|
||||||
|
|
||||||
submitButton.onclick = async () => {
|
submitButton.onclick = async () => {
|
||||||
if (!isUpdate) {
|
if (!isUpdate) {
|
||||||
@ -410,6 +625,10 @@ export class SelectCategoryModal extends Modal {
|
|||||||
await this.plugin.saveSettings();
|
await this.plugin.saveSettings();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存选中的标签
|
||||||
|
this.plugin.settings.selectedTags = Array.from(selectedTags);
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
|
||||||
// 禁用提交按钮,显示加载状态
|
// 禁用提交按钮,显示加载状态
|
||||||
submitButton.disabled = true;
|
submitButton.disabled = true;
|
||||||
submitButton.textContent = isUpdate ? '更新中...' : '发布中...';
|
submitButton.textContent = isUpdate ? '更新中...' : '发布中...';
|
||||||
|
207
styles.css
207
styles.css
@ -10,16 +10,35 @@ If your plugin does not need CSS, delete this file.
|
|||||||
/* 基础模态框样式覆盖 */
|
/* 基础模态框样式覆盖 */
|
||||||
.modal.mod-discourse-sync {
|
.modal.mod-discourse-sync {
|
||||||
max-width: 500px;
|
max-width: 500px;
|
||||||
max-height: 80vh;
|
max-height: 90vh;
|
||||||
background-color: var(--background-primary);
|
background-color: var(--background-primary);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.2);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 内容区域样式 */
|
/* 内容区域样式 */
|
||||||
.discourse-sync-modal {
|
.discourse-sync-modal {
|
||||||
padding: 24px;
|
padding: 24px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 表单区域 */
|
||||||
|
.discourse-sync-modal .form-area {
|
||||||
|
flex-grow: 1;
|
||||||
|
min-height: 0;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 按钮区域固定在底部 */
|
||||||
|
.discourse-sync-modal .button-area {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discourse-sync-modal h1 {
|
.discourse-sync-modal h1 {
|
||||||
@ -29,27 +48,37 @@ If your plugin does not need CSS, delete this file.
|
|||||||
color: var(--text-normal);
|
color: var(--text-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.discourse-sync-modal .select-container {
|
/* 表单容器通用样式 */
|
||||||
|
.discourse-sync-modal .select-container,
|
||||||
|
.discourse-sync-modal .tag-container {
|
||||||
margin-bottom: 24px;
|
margin-bottom: 24px;
|
||||||
|
padding: 0; /* 移除内边距 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.discourse-sync-modal .select-container label {
|
.discourse-sync-modal .select-container label,
|
||||||
|
.discourse-sync-modal .tag-container label {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
color: var(--text-normal);
|
color: var(--text-normal);
|
||||||
}
|
}
|
||||||
|
|
||||||
.discourse-sync-modal select {
|
/* 输入框和选择器样式 */
|
||||||
|
.discourse-sync-modal select,
|
||||||
|
.discourse-sync-modal .tag-select-area {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
height: 42px;
|
|
||||||
line-height: 1.5;
|
|
||||||
border: 2px solid var(--background-modifier-border);
|
border: 2px solid var(--background-modifier-border);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
background-color: var(--background-primary);
|
background-color: var(--background-primary);
|
||||||
color: var(--text-normal);
|
color: var(--text-normal);
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
min-height: 42px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal select {
|
||||||
|
height: 42px;
|
||||||
|
line-height: 1.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discourse-sync-modal select:focus {
|
.discourse-sync-modal select:focus {
|
||||||
@ -141,3 +170,169 @@ If your plugin does not need CSS, delete this file.
|
|||||||
background-color: rgb(255, 82, 82);
|
background-color: rgb(255, 82, 82);
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 标签选择区域样式 */
|
||||||
|
.discourse-sync-modal .tag-select-area {
|
||||||
|
padding: 8px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal .selected-tags {
|
||||||
|
display: none; /* 默认隐藏 */
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
min-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal .selected-tags:not(:empty) {
|
||||||
|
display: flex; /* 当有内容时显示 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal .tag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
padding: 4px 8px;
|
||||||
|
background-color: var(--interactive-accent);
|
||||||
|
color: var(--text-on-accent);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal .remove-tag {
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: rgba(255, 255, 255, 0.2);
|
||||||
|
margin-left: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal .remove-tag:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal input[type="text"] {
|
||||||
|
width: 100%;
|
||||||
|
padding: 8px;
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-normal);
|
||||||
|
font-size: 14px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal .suggestions-container {
|
||||||
|
max-height: 250px;
|
||||||
|
overflow-y: auto;
|
||||||
|
border-bottom: 1px solid var(--background-modifier-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标签输入容器 */
|
||||||
|
.discourse-sync-modal .tag-input-container {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标签建议下拉框 */
|
||||||
|
.discourse-sync-modal .tag-suggestions {
|
||||||
|
position: fixed;
|
||||||
|
background-color: var(--background-primary);
|
||||||
|
border: 1px solid var(--background-modifier-border);
|
||||||
|
border-radius: 4px;
|
||||||
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||||
|
z-index: 1001;
|
||||||
|
max-height: 180px;
|
||||||
|
overflow-y: auto;
|
||||||
|
margin-top: 4px;
|
||||||
|
display: none; /* 默认隐藏 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal .tag-suggestions:not(:empty) {
|
||||||
|
display: block; /* 当有内容时显示 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 标签建议项 */
|
||||||
|
.discourse-sync-modal .tag-suggestion {
|
||||||
|
padding: 8px 12px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--text-normal);
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
background-color: var(--background-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal .tag-suggestion:hover {
|
||||||
|
background-color: var(--background-modifier-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 美化滚动条 */
|
||||||
|
.discourse-sync-modal .tag-suggestions::-webkit-scrollbar {
|
||||||
|
width: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal .tag-suggestions::-webkit-scrollbar-track {
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal .tag-suggestions::-webkit-scrollbar-thumb {
|
||||||
|
background-color: var(--background-modifier-border);
|
||||||
|
border-radius: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal .tag-suggestions::-webkit-scrollbar-thumb:hover {
|
||||||
|
background-color: var(--background-modifier-border-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 移除 tag-container 的 z-index,避免干扰 */
|
||||||
|
.discourse-sync-modal .tag-container {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.discourse-sync-modal .tag-notice {
|
||||||
|
position: fixed;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
padding: 12px 20px;
|
||||||
|
background-color: rgb(255, 235, 235);
|
||||||
|
border: 1px solid rgba(255, 82, 82, 0.2);
|
||||||
|
color: rgb(255, 82, 82);
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
z-index: 1002;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||||
|
max-width: 80%;
|
||||||
|
white-space: nowrap;
|
||||||
|
opacity: 0;
|
||||||
|
animation: fadeInOut 2s ease-in-out forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInOut {
|
||||||
|
0% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
90% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user