mirror of
https://github.com/peritus/obsidian-interactive-ratings.git
synced 2026-07-22 12:20:31 +00:00
Add setting for customizable emoji support in ratings
Added InteractiveRatingsSettings interface, settings tab, and dynamic symbol pattern updates. Users can now customize supported emojis through plugin settings. Default: 🎥🏆⭐💎🔥⚡🎯🚀💰🎖️ Fix regex detection bug for low ratings (1-2 stars) Modified regex to detect 1+ symbols instead of 3+, and updated editor logic to intelligently handle short patterns with rating text or full-only symbols. Fixes issue where reducing ratings to 1-2 stars made them uneditable. Simplify pattern detection logic - remove complex special cases Replaced complex logic with simple rule: if no rating text, require 3+ symbols to avoid false positives. If rating text exists, any symbol count is valid. This generic approach handles all cases without special full-only symbol logic. Remove hardcoded configurable emojis from constants.ts since they're now managed through settings
This commit is contained in:
parent
3c0cf4029a
commit
2ea7af7bbd
7 changed files with 134 additions and 25 deletions
|
|
@ -1,16 +1,30 @@
|
|||
import { Plugin } from 'obsidian';
|
||||
import { LOGGING_ENABLED } from './constants';
|
||||
import { ratingEditorExtension } from './editor-extension';
|
||||
import { InteractiveRatingsSettings } from './types';
|
||||
import { DEFAULT_SETTINGS, InteractiveRatingsSettingTab } from './settings';
|
||||
import { updateSymbolPatternsFromSettings } from './utils';
|
||||
|
||||
export class InteractiveRatingsPlugin extends Plugin {
|
||||
settings: InteractiveRatingsSettings;
|
||||
|
||||
async onload(): Promise<void> {
|
||||
if (LOGGING_ENABLED) {
|
||||
console.info('[InteractiveRatings] Plugin loading - edit mode only');
|
||||
}
|
||||
|
||||
// Load settings
|
||||
await this.loadSettings();
|
||||
|
||||
// Update symbol patterns based on settings
|
||||
this.updateSymbolPatterns();
|
||||
|
||||
// Register editor extension for interactive ratings in editing mode only
|
||||
this.registerEditorExtension(ratingEditorExtension);
|
||||
|
||||
// Add settings tab
|
||||
this.addSettingTab(new InteractiveRatingsSettingTab(this.app, this));
|
||||
|
||||
if (LOGGING_ENABLED) {
|
||||
console.info('[InteractiveRatings] Plugin loaded successfully');
|
||||
}
|
||||
|
|
@ -21,4 +35,16 @@ export class InteractiveRatingsPlugin extends Plugin {
|
|||
console.info('[InteractiveRatings] Plugin unloaded');
|
||||
}
|
||||
}
|
||||
|
||||
async loadSettings(): Promise<void> {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings(): Promise<void> {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
|
||||
updateSymbolPatterns(): void {
|
||||
updateSymbolPatternsFromSettings(this.settings);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@ import { SymbolSet } from './types';
|
|||
// Global logging control - set to false for production, true for debugging
|
||||
export const LOGGING_ENABLED = false;
|
||||
|
||||
// Define symbol patterns as a global constant
|
||||
export const SYMBOL_PATTERNS: SymbolSet[] = [
|
||||
// Base symbol patterns (excluding user-configurable emojis)
|
||||
export const BASE_SYMBOL_PATTERNS: SymbolSet[] = [
|
||||
{ full: '★', empty: '☆', half: null }, // Symbols
|
||||
{ full: '✦', empty: '✧', half: null }, // Star symbols
|
||||
{ full: '🌕', empty: '🌑', half: '🌗' }, // Moon phases
|
||||
|
|
@ -33,20 +33,22 @@ export const SYMBOL_PATTERNS: SymbolSet[] = [
|
|||
{ full: '⬤', empty: '◯', half: null }, // Bold circles
|
||||
{ full: '⚫', empty: '⚪', half: null }, // Black/white circles
|
||||
{ full: '█', empty: '░', half: null }, // Block/light shade
|
||||
|
||||
// Full-only symbols (same symbol for full and empty, no half)
|
||||
{ full: '🎥', empty: '🎥', half: null }, // Movie cameras
|
||||
{ full: '🏆', empty: '🏆', half: null }, // Trophies
|
||||
{ full: '⭐', empty: '⭐', half: null }, // Gold stars
|
||||
{ full: '💎', empty: '💎', half: null }, // Diamonds
|
||||
{ full: '🔥', empty: '🔥', half: null }, // Fire
|
||||
{ full: '⚡', empty: '⚡', half: null }, // Lightning
|
||||
{ full: '🎯', empty: '🎯', half: null }, // Target/bullseye
|
||||
{ full: '🚀', empty: '🚀', half: null }, // Rockets
|
||||
{ full: '💰', empty: '💰', half: null }, // Money bags
|
||||
{ full: '🎖️', empty: '🎖️', half: null }, // Military medals
|
||||
];
|
||||
|
||||
// Mutable symbol patterns - starts with base patterns and user-configurable emojis are added dynamically
|
||||
export let SYMBOL_PATTERNS: SymbolSet[] = [
|
||||
...BASE_SYMBOL_PATTERNS,
|
||||
// User-configurable emojis are added dynamically from settings
|
||||
];
|
||||
|
||||
/**
|
||||
* Update the global symbol patterns array
|
||||
*/
|
||||
export function updateSymbolPatterns(newPatterns: SymbolSet[]): void {
|
||||
SYMBOL_PATTERNS.length = 0; // Clear the array
|
||||
SYMBOL_PATTERNS.push(...newPatterns); // Add new patterns
|
||||
}
|
||||
|
||||
// Interaction constants
|
||||
export const INTERACTION_BUFFER = 5; // Buffer for interaction detection
|
||||
export const OVERLAY_VERTICAL_ADJUSTMENT = 2.1; // Vertical alignment for overlay
|
||||
|
|
@ -416,10 +416,6 @@ const ratingViewPlugin = ViewPlugin.fromClass(
|
|||
const start = match.index;
|
||||
const end = start + pattern.length;
|
||||
|
||||
// Skip if too short (minimum 3 symbols) - use Unicode length
|
||||
const unicodeLength = getUnicodeCharLength(pattern);
|
||||
if (unicodeLength < 3) continue;
|
||||
|
||||
const symbolSet = getSymbolSetForPattern(pattern);
|
||||
if (!symbolSet) continue;
|
||||
|
||||
|
|
@ -432,8 +428,11 @@ const ratingViewPlugin = ViewPlugin.fromClass(
|
|||
// Check for rating text after the symbols - pass UTF-16 positions
|
||||
const ratingText = parseRatingText(text, start, end);
|
||||
|
||||
// For full-only symbols without rating text, they can still be interactive
|
||||
// (rating text will be auto-added on first interaction)
|
||||
// Simple rule: if no rating text, require minimum 3 symbols to avoid false positives
|
||||
const unicodeLength = getUnicodeCharLength(pattern);
|
||||
if (!ratingText && unicodeLength < 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const actualEnd = ratingText ? ratingText.endPosition : end;
|
||||
|
||||
|
|
@ -515,6 +514,7 @@ const ratingViewPlugin = ViewPlugin.fromClass(
|
|||
const fullOnlyCount = filteredMatches.filter(m => isFullOnlySymbol(m.symbolSet)).length;
|
||||
const commentCount = filteredMatches.filter(m => m.ratingText && m.ratingText.format === 'comment-fraction').length;
|
||||
const autoAddCandidates = filteredMatches.filter(m => isFullOnlySymbol(m.symbolSet) && !m.ratingText).length;
|
||||
const shortPatternCount = filteredMatches.filter(m => getUnicodeCharLength(m.pattern) < 3).length;
|
||||
console.debug(`[InteractiveRatings] Built ${filteredMatches.length - skippedCount}/${filteredMatches.length} rating decorations (${skippedCount} skipped due to cursor proximity)`, {
|
||||
cursorPos,
|
||||
withRatingText: filteredMatches.filter(m => m.ratingText).length,
|
||||
|
|
@ -522,7 +522,8 @@ const ratingViewPlugin = ViewPlugin.fromClass(
|
|||
withHalfSymbols: filteredMatches.filter(m => m.symbolSet.half).length,
|
||||
fullOnlySymbols: fullOnlyCount,
|
||||
commentFormatSymbols: commentCount,
|
||||
autoAddCandidates
|
||||
autoAddCandidates,
|
||||
shortPatterns: shortPatternCount
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -167,6 +167,7 @@ function escapeRegexChar(char: string): string {
|
|||
/**
|
||||
* Generate regex patterns for detecting rating symbols in text
|
||||
* Fixed to handle multibyte Unicode characters (emojis) properly
|
||||
* Now supports 1+ symbols to detect low ratings
|
||||
*/
|
||||
export function generateSymbolRegexPatterns(): RegExp[] {
|
||||
return SYMBOL_PATTERNS.map(symbols => {
|
||||
|
|
@ -178,13 +179,15 @@ export function generateSymbolRegexPatterns(): RegExp[] {
|
|||
|
||||
// Escape each symbol for regex and create alternation
|
||||
const escapedSymbols = symbolChars.map(escapeRegexChar);
|
||||
const pattern = `(?:${escapedSymbols.join('|')}){3,}`;
|
||||
// Changed from {3,} to {1,} to detect single symbols (fixes low rating bug)
|
||||
const pattern = `(?:${escapedSymbols.join('|')})+`;
|
||||
|
||||
if (LOGGING_ENABLED) {
|
||||
console.debug(`[InteractiveRatings] Generated regex pattern for symbol set`, {
|
||||
symbolSet: symbols,
|
||||
pattern,
|
||||
escapedSymbols
|
||||
escapedSymbols,
|
||||
allowsSingleSymbol: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
39
src/settings.ts
Normal file
39
src/settings.ts
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
import { App, PluginSettingTab, Setting } from 'obsidian';
|
||||
import { InteractiveRatingsPlugin } from './InteractiveRatingsPlugin';
|
||||
import { InteractiveRatingsSettings } from './types';
|
||||
|
||||
const DEFAULT_SUPPORTED_EMOJIS = '🎥🏆⭐💎🔥⚡🎯🚀💰🎖️';
|
||||
|
||||
export const DEFAULT_SETTINGS: InteractiveRatingsSettings = {
|
||||
supportedEmojis: DEFAULT_SUPPORTED_EMOJIS
|
||||
};
|
||||
|
||||
export class InteractiveRatingsSettingTab extends PluginSettingTab {
|
||||
plugin: InteractiveRatingsPlugin;
|
||||
|
||||
constructor(app: App, plugin: InteractiveRatingsPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', { text: 'Interactive Ratings Settings' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Emojis to support in ratings')
|
||||
.setDesc('Emojis to use for ratings.')
|
||||
.addTextArea(text => text
|
||||
.setPlaceholder(DEFAULT_SUPPORTED_EMOJIS)
|
||||
.setValue(this.plugin.settings.supportedEmojis)
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.supportedEmojis = value;
|
||||
await this.plugin.saveSettings();
|
||||
// Update the symbol patterns with new emojis
|
||||
this.plugin.updateSymbolPatterns();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -11,3 +11,7 @@ export interface RatingText {
|
|||
text: string;
|
||||
endPosition: number;
|
||||
}
|
||||
|
||||
export interface InteractiveRatingsSettings {
|
||||
supportedEmojis: string;
|
||||
}
|
||||
38
src/utils.ts
38
src/utils.ts
|
|
@ -1,5 +1,5 @@
|
|||
import { LOGGING_ENABLED } from './constants';
|
||||
import { SymbolSet } from './types';
|
||||
import { LOGGING_ENABLED, BASE_SYMBOL_PATTERNS, updateSymbolPatterns } from './constants';
|
||||
import { SymbolSet, InteractiveRatingsSettings } from './types';
|
||||
|
||||
/**
|
||||
* Get the length of a string in Unicode characters
|
||||
|
|
@ -32,6 +32,40 @@ export function isFullOnlySymbol(symbolSet: SymbolSet): boolean {
|
|||
return symbolSet.full === symbolSet.empty && !symbolSet.half;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update symbol patterns based on user settings
|
||||
*/
|
||||
export function updateSymbolPatternsFromSettings(settings: InteractiveRatingsSettings): void {
|
||||
// Start with base patterns (all the existing ones except the customizable emojis)
|
||||
const newPatterns = [...BASE_SYMBOL_PATTERNS];
|
||||
|
||||
// Add emoji patterns from settings
|
||||
if (settings.supportedEmojis) {
|
||||
// Split the emoji string into individual characters (handling Unicode properly)
|
||||
const emojis = [...settings.supportedEmojis];
|
||||
|
||||
for (const emoji of emojis) {
|
||||
if (emoji.trim()) { // Skip empty characters
|
||||
newPatterns.push({
|
||||
full: emoji,
|
||||
empty: emoji,
|
||||
half: null
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update the global symbol patterns
|
||||
updateSymbolPatterns(newPatterns);
|
||||
|
||||
if (LOGGING_ENABLED) {
|
||||
console.debug('[InteractiveRatings] Updated symbol patterns from settings', {
|
||||
supportedEmojis: settings.supportedEmojis,
|
||||
totalPatterns: newPatterns.length
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the new rating based on cursor position
|
||||
*/
|
||||
|
|
|
|||
Loading…
Reference in a new issue