mirror of
https://github.com/lossless-group/perplexed-plugin.git
synced 2026-07-22 06:49:50 +00:00
improve(settings): improve settings UI
On branch development Your branch is up to date with 'origin/development'. Changes to be committed: modified: main.ts new file: package-lock.json modified: package.json modified: styles.css
This commit is contained in:
parent
7e6831c315
commit
c81ca99aa8
4 changed files with 425 additions and 212 deletions
545
main.ts
545
main.ts
|
|
@ -1,155 +1,456 @@
|
|||
import { Notice, Plugin, Editor } from 'obsidian';
|
||||
import { citationService } from './src/services/citationService';
|
||||
import { CitationModal } from './src/modals/CitationModal';
|
||||
import { cleanReferencesSectionService } from './src/services/cleanReferencesSectionService';
|
||||
import { App, Editor, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
import * as dotenv from 'dotenv';
|
||||
|
||||
// Load environment variables
|
||||
dotenv.config({ path: `${process.cwd()}/.env` });
|
||||
|
||||
interface PerplexedPluginSettings {
|
||||
mySetting: string;
|
||||
localLLMPath: string;
|
||||
requestBodyTemplate: string;
|
||||
perplexityRequestTemplate: string;
|
||||
perplexityApiKey: string;
|
||||
perplexicaEndpoint: string;
|
||||
perplexityEndpoint: string;
|
||||
defaultModel: string;
|
||||
defaultOptimizationMode: string;
|
||||
defaultFocusMode: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: PerplexedPluginSettings = {
|
||||
mySetting: 'default',
|
||||
// Use host.docker.internal to connect to the host machine from Docker containers
|
||||
localLLMPath: 'http://host.docker.internal:3030/api/search',
|
||||
perplexicaEndpoint: 'http://localhost:3030/api/search',
|
||||
perplexityEndpoint: 'https://api.perplexity.ai/chat/completions',
|
||||
requestBodyTemplate: `{
|
||||
"chatModel": {
|
||||
"provider": "ollama",
|
||||
"name": "llama3.2:latest"
|
||||
},
|
||||
"embeddingModel": {
|
||||
"provider": "ollama",
|
||||
"name": "llama3.2:latest"
|
||||
},
|
||||
"optimizationMode": "speed",
|
||||
"focusMode": "webSearch",
|
||||
"query": "What is Perplexica's architecture?",
|
||||
"history": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What is Perplexica's architecture?"
|
||||
}
|
||||
],
|
||||
"systemInstructions": "You are a helpful AI assistant. Provide clear, concise, and accurate information.",
|
||||
"stream": false,
|
||||
"maxTokens": 2048,
|
||||
"temperature": 0.7
|
||||
}`,
|
||||
perplexityApiKey: process.env.PERPLEXITY_API_KEY || '',
|
||||
perplexityRequestTemplate: `{
|
||||
"model": "llama-3.1-sonar-small-128k-online",
|
||||
"messages": [
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a helpful AI assistant. Provide clear, concise, and accurate information with proper citations."
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": "What is Perplexity AI's approach to search?"
|
||||
}
|
||||
],
|
||||
"max_tokens": 2048,
|
||||
"temperature": 0.7,
|
||||
"top_p": 0.9,
|
||||
"return_citations": true,
|
||||
"search_domain_filter": [],
|
||||
"return_images": false,
|
||||
"return_related_questions": false,
|
||||
"search_recency_filter": "month",
|
||||
"top_k": 0,
|
||||
"stream": false,
|
||||
"presence_penalty": 0,
|
||||
"frequency_penalty": 1
|
||||
}`,
|
||||
defaultModel: 'llama3.2:latest',
|
||||
defaultOptimizationMode: 'speed',
|
||||
defaultFocusMode: 'webSearch'
|
||||
};
|
||||
|
||||
export default class PerplexedPlugin extends Plugin {
|
||||
public settings: PerplexedPluginSettings = DEFAULT_SETTINGS;
|
||||
private statusBarItemEl: HTMLElement | null = null;
|
||||
private ribbonIconEl: HTMLElement | null = null;
|
||||
|
||||
export default class CiteWidePlugin extends Plugin {
|
||||
async onload(): Promise<void> {
|
||||
// Load CSS
|
||||
this.loadStyles();
|
||||
await this.loadSettings();
|
||||
|
||||
// Debug: Log current settings
|
||||
console.log('Current Perplexica Path:', this.settings.perplexicaEndpoint);
|
||||
console.log('Full settings:', JSON.stringify(this.settings, null, 2));
|
||||
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new PerplexedSettingTab(this.app, this));
|
||||
|
||||
// Register commands
|
||||
this.registerCitationCommands();
|
||||
this.registerReferenceCleanupCommands();
|
||||
this.registerCitationFormattingCommands();
|
||||
this.registerPerplexicaCommands();
|
||||
this.registerPerplexityCommands();
|
||||
}
|
||||
|
||||
private async loadStyles() {
|
||||
|
||||
onunload(): void {
|
||||
this.statusBarItemEl?.remove();
|
||||
this.ribbonIconEl?.remove();
|
||||
}
|
||||
|
||||
private async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
public async saveSettings(): Promise<void> {
|
||||
try {
|
||||
const cssPath = this.manifest.dir + '/styles.css';
|
||||
const response = await fetch(cssPath);
|
||||
if (!response.ok) throw new Error('Failed to load CSS');
|
||||
|
||||
const css = await response.text();
|
||||
const styleEl = document.createElement('style');
|
||||
styleEl.id = 'cite-wide-styles';
|
||||
styleEl.textContent = css;
|
||||
document.head.appendChild(styleEl);
|
||||
await this.saveData(this.settings);
|
||||
} catch (error) {
|
||||
console.error('Error loading styles:', error);
|
||||
console.error('Failed to save settings:', error);
|
||||
new Notice('Failed to save settings');
|
||||
}
|
||||
}
|
||||
|
||||
private registerCitationCommands(): void {
|
||||
// Command to show citations in current file
|
||||
private registerPerplexicaCommands(): void {
|
||||
// Command to update Perplexica URL
|
||||
this.addCommand({
|
||||
id: 'show-citations',
|
||||
name: 'Show Citations in Current File',
|
||||
editorCallback: (editor: Editor) => {
|
||||
new CitationModal(this.app, editor).open();
|
||||
}
|
||||
});
|
||||
id: 'update-perplexica-url',
|
||||
name: 'Update Perplexica URL',
|
||||
callback: () => {
|
||||
const modal = new (class extends Modal {
|
||||
private urlInput!: HTMLInputElement;
|
||||
|
||||
// Command to convert all citations to hex format
|
||||
this.addCommand({
|
||||
id: 'convert-all-citations',
|
||||
name: 'Convert All Citations to Hex Format',
|
||||
editorCallback: async (editor: Editor) => {
|
||||
try {
|
||||
const content = editor.getValue();
|
||||
// Get all citation groups
|
||||
const groups = citationService.findCitations(content);
|
||||
let totalConverted = 0;
|
||||
let updatedContent = content;
|
||||
constructor(app: App, private plugin: PerplexedPlugin) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
// Convert each citation group
|
||||
for (const group of groups) {
|
||||
const result = citationService.convertCitation(updatedContent, group.number);
|
||||
if (result.changed) {
|
||||
updatedContent = result.content;
|
||||
totalConverted += result.stats.citationsConverted;
|
||||
onOpen() {
|
||||
const {contentEl} = this;
|
||||
contentEl.createEl('h2', {text: 'Update Perplexica API URL'});
|
||||
|
||||
const form = contentEl.createEl('form');
|
||||
const div = form.createDiv({cls: 'setting-item'});
|
||||
|
||||
div.createEl('label', {
|
||||
text: 'Perplexica API URL',
|
||||
attr: {for: 'perplexica-url-input'}
|
||||
});
|
||||
|
||||
this.urlInput = div.createEl('input', {
|
||||
type: 'text',
|
||||
value: this.plugin.settings.perplexicaEndpoint,
|
||||
cls: 'text-input',
|
||||
attr: {id: 'perplexica-url-input'}
|
||||
});
|
||||
|
||||
const buttonDiv = contentEl.createDiv({cls: 'setting-item'});
|
||||
const saveButton = buttonDiv.createEl('button', {
|
||||
text: 'Save',
|
||||
cls: 'mod-cta'
|
||||
});
|
||||
|
||||
form.onsubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.onSubmit();
|
||||
};
|
||||
|
||||
saveButton.onclick = () => this.onSubmit();
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
const newUrl = this.urlInput.value.trim();
|
||||
if (newUrl) {
|
||||
this.plugin.settings.perplexicaEndpoint = newUrl;
|
||||
this.plugin.saveSettings();
|
||||
new Notice(`Perplexica URL updated to: ${newUrl}`);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
if (totalConverted > 0) {
|
||||
editor.setValue(updatedContent);
|
||||
new Notice(`Updated ${totalConverted} citations`);
|
||||
} else {
|
||||
new Notice('No citations needed conversion');
|
||||
onClose() {
|
||||
const {contentEl} = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
new Notice('Error processing citations: ' + errorMsg);
|
||||
}
|
||||
})(this.app, this);
|
||||
|
||||
modal.open();
|
||||
}
|
||||
});
|
||||
|
||||
// Command to show current settings
|
||||
this.addCommand({
|
||||
id: 'show-perplexica-settings',
|
||||
name: 'Show Perplexica Settings',
|
||||
callback: () => {
|
||||
new Notice(`Current Perplexica URL: ${this.settings.perplexicaEndpoint}`);
|
||||
console.log('Perplexica Settings:', this.settings);
|
||||
}
|
||||
});
|
||||
|
||||
// Command to insert a new citation
|
||||
// Command to insert Perplexica request template
|
||||
this.addCommand({
|
||||
id: 'insert-hex-citation',
|
||||
name: 'Insert Hex Citation',
|
||||
id: 'insert-perplexica-template',
|
||||
name: 'Insert Perplexica Request Template',
|
||||
editorCallback: (editor: Editor) => {
|
||||
try {
|
||||
const cursor = editor.getCursor();
|
||||
const hexId = citationService.getNewHexId();
|
||||
|
||||
// Insert the citation reference at cursor
|
||||
editor.replaceRange(`[^${hexId}]`, cursor);
|
||||
|
||||
// Add the footnote definition at the end
|
||||
const content = editor.getValue();
|
||||
const footnotePosition = {
|
||||
line: content.split('\n').length,
|
||||
ch: 0
|
||||
};
|
||||
|
||||
editor.replaceRange(`\n\n[^${hexId}]: `, footnotePosition);
|
||||
|
||||
// Position cursor after the inserted citation
|
||||
const newPos = {
|
||||
line: footnotePosition.line + 2, // +2 for the two newlines
|
||||
ch: `[^${hexId}]: `.length
|
||||
};
|
||||
editor.setCursor(newPos);
|
||||
|
||||
} catch (error) {
|
||||
const errorMsg = error instanceof Error ? error.message : String(error);
|
||||
new Notice('Error inserting citation: ' + errorMsg);
|
||||
}
|
||||
const template = this.settings.requestBodyTemplate;
|
||||
editor.replaceSelection(
|
||||
'```json\n' +
|
||||
template + '\n' +
|
||||
'```'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private registerReferenceCleanupCommands(): void {
|
||||
// Command to clean up references section
|
||||
private registerPerplexityCommands(): void {
|
||||
// Command to update Perplexity URL
|
||||
this.addCommand({
|
||||
id: 'clean-references-section',
|
||||
name: 'Add Colon to Footnote References in Selection',
|
||||
editorCallback: (editor: Editor) => {
|
||||
const selection = editor.getSelection();
|
||||
if (!selection) {
|
||||
new Notice('Please select some text first');
|
||||
return;
|
||||
}
|
||||
|
||||
const processed = cleanReferencesSectionService.addColonSyntaxWhereNone(selection);
|
||||
editor.replaceSelection(processed);
|
||||
new Notice('References cleaned up successfully');
|
||||
id: 'update-perplexity-url',
|
||||
name: 'Update Perplexity URL',
|
||||
callback: () => {
|
||||
const modal = new (class extends Modal {
|
||||
private urlInput!: HTMLInputElement;
|
||||
|
||||
constructor(app: App, private plugin: PerplexedPlugin) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const {contentEl} = this;
|
||||
contentEl.createEl('h2', {text: 'Update Perplexity API URL'});
|
||||
const form = contentEl.createEl('form');
|
||||
const div = form.createDiv({cls: 'setting-item'});
|
||||
|
||||
div.createEl('label', {
|
||||
text: 'Perplexity API URL',
|
||||
attr: {for: 'perplexity-url-input'}
|
||||
});
|
||||
|
||||
this.urlInput = div.createEl('input', {
|
||||
type: 'text',
|
||||
value: this.plugin.settings.perplexityEndpoint,
|
||||
cls: 'text-input',
|
||||
attr: {id: 'perplexity-url-input'}
|
||||
});
|
||||
|
||||
const buttonDiv = contentEl.createDiv({cls: 'setting-item'});
|
||||
const saveButton = buttonDiv.createEl('button', {
|
||||
text: 'Save',
|
||||
cls: 'mod-cta'
|
||||
});
|
||||
|
||||
form.onsubmit = (e) => {
|
||||
e.preventDefault();
|
||||
this.onSubmit();
|
||||
};
|
||||
|
||||
saveButton.onclick = () => this.onSubmit();
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
const newUrl = this.urlInput.value.trim();
|
||||
if (newUrl) {
|
||||
this.plugin.settings.perplexityEndpoint = newUrl;
|
||||
this.plugin.saveSettings();
|
||||
new Notice(`Perplexity URL updated to: ${newUrl}`);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const {contentEl} = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
})(this.app, this);
|
||||
|
||||
modal.open();
|
||||
}
|
||||
});
|
||||
|
||||
// Command to show current Perplexity settings
|
||||
this.addCommand({
|
||||
id: 'show-perplexity-settings',
|
||||
name: 'Show Perplexity Settings',
|
||||
callback: () => {
|
||||
new Notice(`Current Perplexity URL: ${this.settings.perplexityEndpoint}`);
|
||||
console.log('Perplexity Settings:', this.settings);
|
||||
}
|
||||
});
|
||||
|
||||
// Command to insert Perplexity request template
|
||||
this.addCommand({
|
||||
id: 'insert-perplexity-template',
|
||||
name: 'Insert Perplexity Request Template',
|
||||
editorCallback: (editor: Editor) => {
|
||||
const template = this.settings.perplexityRequestTemplate;
|
||||
editor.replaceSelection(
|
||||
'```json\n' +
|
||||
template + '\n' +
|
||||
'```'
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class PerplexedSettingTab extends PluginSettingTab {
|
||||
plugin: PerplexedPlugin;
|
||||
|
||||
constructor(app: App, plugin: PerplexedPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
private registerCitationFormattingCommands(): void {
|
||||
// Command to format citations by moving them after punctuation and ensuring proper spacing
|
||||
this.addCommand({
|
||||
id: 'format-citations-punctuation',
|
||||
name: 'Move Citations after Punctuation',
|
||||
editorCallback: (editor: Editor) => {
|
||||
// Process the entire document content
|
||||
const content = editor.getValue();
|
||||
|
||||
// First move citations after punctuation, then ensure proper spacing
|
||||
let processed = citationService.moveCitationsBehindPunctuation(content);
|
||||
processed = citationService.assureSpacingBetweenCitations(processed);
|
||||
|
||||
// Only update if there were changes
|
||||
if (processed !== content) {
|
||||
editor.setValue(processed);
|
||||
new Notice('Formatted citations in document');
|
||||
} else {
|
||||
new Notice('No citations needed formatting');
|
||||
}
|
||||
}
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', { text: 'Perplexed Plugin Settings' });
|
||||
|
||||
// Perplexity Section
|
||||
containerEl.createEl('h3', { text: 'Perplexity (Remote Service)' });
|
||||
containerEl.createEl('p', {
|
||||
text: 'Configure settings for the hosted Perplexity AI service',
|
||||
cls: 'setting-item-description'
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Endpoint')
|
||||
.setDesc('API endpoint for Perplexity service')
|
||||
.addText(text => text
|
||||
.setPlaceholder('https://api.perplexity.ai/chat/completions')
|
||||
.setValue(this.plugin.settings.perplexityEndpoint)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.perplexityEndpoint = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('API Key')
|
||||
.setDesc('Your Perplexity API key (required for remote service)')
|
||||
.addText(text => text
|
||||
.setPlaceholder('pplx-xxxxxxxxxxxxxxxxxxxxx')
|
||||
.setValue(this.plugin.settings.perplexityApiKey)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.perplexityApiKey = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// Perplexica Section
|
||||
containerEl.createEl('h3', { text: 'Perplexica (Self-Hosted)' });
|
||||
containerEl.createEl('p', {
|
||||
text: 'Configure settings for your local Perplexica installation',
|
||||
cls: 'setting-item-description'
|
||||
});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Endpoint')
|
||||
.setDesc('API endpoint for your local Perplexica instance')
|
||||
.addText(text => text
|
||||
.setPlaceholder('http://localhost:3030/api/search')
|
||||
.setValue(this.plugin.settings.perplexicaEndpoint)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.perplexicaEndpoint = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Fallback Container Path')
|
||||
.setDesc('Alternative endpoint for Docker container setups')
|
||||
.addText(text => text
|
||||
.setPlaceholder('http://host.docker.internal:3030/api/search')
|
||||
.setValue(this.plugin.settings.localLLMPath)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.localLLMPath = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Default Model')
|
||||
.setDesc('Default AI model for Perplexica to use')
|
||||
.addText(text => text
|
||||
.setPlaceholder('llama3.2:latest')
|
||||
.setValue(this.plugin.settings.defaultModel)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.defaultModel = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// Perplexity Request Template
|
||||
const perplexityJsonSetting = new Setting(containerEl)
|
||||
.setName('Request Body Template')
|
||||
.setDesc('JSON template for Perplexity API requests');
|
||||
|
||||
// Create a textarea element for Perplexity
|
||||
const perplexityTextArea = document.createElement('textarea');
|
||||
perplexityTextArea.rows = 10;
|
||||
perplexityTextArea.cols = 50;
|
||||
perplexityTextArea.style.width = '100%';
|
||||
perplexityTextArea.style.minHeight = '300px';
|
||||
perplexityTextArea.style.fontFamily = 'monospace';
|
||||
perplexityTextArea.placeholder = 'Enter Perplexity JSON request template...';
|
||||
|
||||
// Set initial value if it exists
|
||||
if (this.plugin.settings.perplexityRequestTemplate) {
|
||||
try {
|
||||
const config = JSON.parse(this.plugin.settings.perplexityRequestTemplate);
|
||||
perplexityTextArea.value = JSON.stringify(config, null, 2);
|
||||
} catch (e) {
|
||||
// If not valid JSON, use as is
|
||||
perplexityTextArea.value = this.plugin.settings.perplexityRequestTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
// Add input event listener for Perplexity
|
||||
perplexityTextArea.addEventListener('input', async () => {
|
||||
this.plugin.settings.perplexityRequestTemplate = perplexityTextArea.value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
|
||||
// Add the textarea to the setting
|
||||
perplexityJsonSetting.settingEl.appendChild(perplexityTextArea);
|
||||
|
||||
// Perplexica Request Template
|
||||
const perplexicaJsonSetting = new Setting(containerEl)
|
||||
.setName('Request Body Template')
|
||||
.setDesc('JSON template for Perplexica API requests');
|
||||
|
||||
// Create a textarea element for Perplexica
|
||||
const perplexicaTextArea = document.createElement('textarea');
|
||||
perplexicaTextArea.rows = 10;
|
||||
perplexicaTextArea.cols = 50;
|
||||
perplexicaTextArea.style.width = '100%';
|
||||
perplexicaTextArea.style.minHeight = '300px';
|
||||
perplexicaTextArea.style.fontFamily = 'monospace';
|
||||
perplexicaTextArea.placeholder = 'Enter Perplexica JSON request template...';
|
||||
|
||||
// Set initial value if it exists
|
||||
if (this.plugin.settings.requestBodyTemplate) {
|
||||
try {
|
||||
const config = JSON.parse(this.plugin.settings.requestBodyTemplate);
|
||||
perplexicaTextArea.value = JSON.stringify(config, null, 2);
|
||||
} catch (e) {
|
||||
// If not valid JSON, use as is
|
||||
perplexicaTextArea.value = this.plugin.settings.requestBodyTemplate;
|
||||
}
|
||||
}
|
||||
|
||||
// Add input event listener for Perplexica
|
||||
perplexicaTextArea.addEventListener('input', async () => {
|
||||
this.plugin.settings.requestBodyTemplate = perplexicaTextArea.value;
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
|
||||
// Add the textarea to the setting
|
||||
perplexicaJsonSetting.settingEl.appendChild(perplexicaTextArea);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
BIN
package-lock.json
generated
Normal file
BIN
package-lock.json
generated
Normal file
Binary file not shown.
|
|
@ -34,6 +34,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@modelcontextprotocol/sdk": "^1.15.0",
|
||||
"dotenv": "^17.2.0",
|
||||
"fastify": "^5.4.0",
|
||||
"zod": "^4.0.0"
|
||||
}
|
||||
|
|
|
|||
91
styles.css
91
styles.css
|
|
@ -1,90 +1 @@
|
|||
/* src/styles/citations.css */
|
||||
.cite-wide-modal {
|
||||
padding: 1.5rem;
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.cite-wide-title {
|
||||
margin-top: 0;
|
||||
padding-bottom: 0.75rem;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
.cite-wide-container {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
.cite-wide-group {
|
||||
margin-bottom: 1.5rem;
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.cite-wide-group-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
background-color: var(--background-secondary);
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
.cite-wide-group-header:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
}
|
||||
.cite-wide-group-header-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.cite-wide-group-title {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
.cite-wide-source-link {
|
||||
font-size: 0.85rem;
|
||||
opacity: 0.8;
|
||||
text-decoration: none;
|
||||
}
|
||||
.cite-wide-group-content {
|
||||
padding: 0.75rem 1rem;
|
||||
background-color: var(--background-primary);
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
.cite-wide-instance {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.5rem 0;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
.cite-wide-instance:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.cite-wide-line-info {
|
||||
flex: 1;
|
||||
font-family: var(--font-monospace);
|
||||
font-size: 0.9em;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.cite-wide-line-number {
|
||||
color: var(--text-muted);
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
.cite-wide-line-preview {
|
||||
opacity: 0.9;
|
||||
}
|
||||
.cite-wide-convert-btn {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.cite-wide-view-btn {
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
.cite-wide-footer {
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
.cite-wide-modal{padding:1.5rem;max-width:800px;margin:0 auto}.cite-wide-title{margin-top:0;padding-bottom:.75rem;border-bottom:1px solid var(--background-modifier-border)}.cite-wide-container{margin-top:1rem}.cite-wide-group{margin-bottom:1.5rem;border:1px solid var(--background-modifier-border);border-radius:6px;overflow:hidden}.cite-wide-group-header{display:flex;justify-content:space-between;align-items:center;padding:.75rem 1rem;background-color:var(--background-secondary);cursor:pointer;transition:background-color .2s}.cite-wide-group-header:hover{background-color:var(--background-modifier-hover)}.cite-wide-group-header-content{display:flex;align-items:center;gap:.5rem}.cite-wide-group-title{margin:0;font-size:1rem;font-weight:600}.cite-wide-source-link{font-size:.85rem;opacity:.8;text-decoration:none}.cite-wide-group-content{padding:.75rem 1rem;background-color:var(--background-primary);border-top:1px solid var(--background-modifier-border)}.cite-wide-instance{display:flex;justify-content:space-between;align-items:center;padding:.5rem 0;border-bottom:1px solid var(--background-modifier-border)}.cite-wide-instance.cite-wide-reference-source{background-color:var(--background-secondary-alt);margin:0 -1rem;padding:.75rem 1rem;border-radius:4px;border:1px solid var(--background-modifier-border);border-left:3px solid var(--interactive-accent)}.cite-wide-instance:last-child{border-bottom:none}.cite-wide-line-info{flex:1;font-family:var(--font-monospace);font-size:.9em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.cite-wide-line-number{color:var(--text-muted);margin-right:.5rem}.cite-wide-line-preview{opacity:.9}.cite-wide-badge{display:inline-block;padding:.15em .5em;font-size:.75em;font-weight:600;line-height:1.2;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:3px;text-transform:uppercase;letter-spacing:.5px}.cite-wide-badge-reference{color:var(--text-normal);background-color:var(--background-modifier-border)}.cite-wide-line-info>.cite-wide-badge+span{margin-left:.5rem}.cite-wide-instance-actions{display:flex;gap:.5rem;margin-left:.75rem}.cite-wide-convert-btn,.cite-wide-view-btn{white-space:nowrap;padding:.25rem .75rem;font-size:.85em;line-height:1.4;border-radius:4px;transition:all .1s ease}.cite-wide-convert-btn{background-color:var(--interactive-accent);color:var(--text-on-accent);border:1px solid var(--interactive-accent-hover)}.cite-wide-convert-btn:hover{background-color:var(--interactive-accent-hover)}.cite-wide-view-btn{background-color:var(--background-secondary);border:1px solid var(--background-modifier-border)}.cite-wide-view-btn:hover{background-color:var(--background-modifier-hover)}.cite-wide-footer{margin-top:1.5rem;padding-top:1rem;border-top:1px solid var(--background-modifier-border);display:flex;justify-content:flex-end}
|
||||
|
|
|
|||
Loading…
Reference in a new issue