mirror of
https://github.com/stracker-phil/obsidian-mention-things.git
synced 2026-07-22 05:43:27 +00:00
👔 Major changes for v 1.10
This commit is contained in:
parent
83b1460439
commit
4dd5f9abaf
7 changed files with 169 additions and 50 deletions
|
|
@ -7,11 +7,14 @@ export const DEFAULT_SETTINGS: MentionSettings = {
|
|||
mentionTypes: [],
|
||||
matchStart: true,
|
||||
maxMatchLength: 15,
|
||||
stopCharacters: '?!"\'`:;/#+*=&%$§<>',
|
||||
stopCharacters: '?!;:"\'`/#*%<>[]()',
|
||||
};
|
||||
|
||||
/**
|
||||
* List of allowed signs for mentions
|
||||
*
|
||||
* Forbidden on Windows: " * : < > ? \ / |
|
||||
* Conflict with Obsidian: [ ] ( ) | # % `
|
||||
*/
|
||||
export const ALLOWED_SIGNS_STRING = '@#+!$%^&*~=-:;><|';
|
||||
export const ALLOWED_SIGNS_STRING = "@!+$&~-_=,;'{}^§★→¶°€£¥¢±µ©™®«»";
|
||||
export const ALLOWED_SIGNS = ALLOWED_SIGNS_STRING.split('');
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { App, EditorSuggest, Editor, EditorPosition, TFile } from 'obsidian';
|
||||
import { MentionManager } from '../mention/mention-manager';
|
||||
import { getTypeDef, createMentionLink } from '../mention/link-utils';
|
||||
import { getTypeDef, createMentionString } from '../mention/link-utils';
|
||||
import {
|
||||
MentionSettings,
|
||||
MentionSuggestion,
|
||||
|
|
@ -16,6 +16,7 @@ export class SuggestionProvider extends EditorSuggest<MentionSuggestion> {
|
|||
private settings: MentionSettings;
|
||||
private mentionManager: MentionManager;
|
||||
private currSign: string = '';
|
||||
private prevMention: string = '';
|
||||
private fileMaps: any;
|
||||
|
||||
constructor(app: App, settings: MentionSettings, mentionManager: MentionManager) {
|
||||
|
|
@ -25,6 +26,10 @@ export class SuggestionProvider extends EditorSuggest<MentionSuggestion> {
|
|||
this.fileMaps = mentionManager.getFileMaps();
|
||||
}
|
||||
|
||||
get currType() {
|
||||
return getTypeDef(this.settings.mentionTypes, this.currSign);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the suggestions map
|
||||
*/
|
||||
|
|
@ -92,18 +97,28 @@ export class SuggestionProvider extends EditorSuggest<MentionSuggestion> {
|
|||
return false;
|
||||
}
|
||||
|
||||
// Check if query includes closing brackets
|
||||
if (query.includes(']]')) {
|
||||
// Verify if the current query is different from the previous completion
|
||||
if (this.prevMention) {
|
||||
if (query.startsWith(this.prevMention)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Reset the prev completion cache
|
||||
this.prevMention = '';
|
||||
}
|
||||
|
||||
// Is WikiLink?
|
||||
if (/\[\[.*]]/.test(query) || /\[.*]\(.*\)/.test(query)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if query exceeds max length
|
||||
// Check if the query exceeds max length
|
||||
const maxMatchLength = this.settings.maxMatchLength ?? DEFAULT_SETTINGS.maxMatchLength;
|
||||
if (maxMatchLength && query.length > maxMatchLength) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if query contains any stop characters
|
||||
// Check if the query contains any stop characters
|
||||
const stopCharacters = this.settings.stopCharacters ?? DEFAULT_SETTINGS.stopCharacters;
|
||||
if (stopCharacters) {
|
||||
for (const char of stopCharacters) {
|
||||
|
|
@ -113,7 +128,7 @@ export class SuggestionProvider extends EditorSuggest<MentionSuggestion> {
|
|||
}
|
||||
}
|
||||
|
||||
// Check if sign is at start of line or has a space before it
|
||||
// Check if the sign is at the start of the line or has a space before it.
|
||||
return (
|
||||
signIndex === 0 ||
|
||||
charsLeftOfCursor[signIndex - 1] === ' '
|
||||
|
|
@ -124,14 +139,14 @@ export class SuggestionProvider extends EditorSuggest<MentionSuggestion> {
|
|||
* Get suggestions based on the query
|
||||
*/
|
||||
getSuggestions(context: EditorSuggestContext): MentionSuggestion[] {
|
||||
let suggestions: MentionSuggestion[] = [];
|
||||
let suggestions: MentionSuggestion[];
|
||||
let map = this.fileMaps[this.currSign] || {};
|
||||
const term = context.query.toLowerCase();
|
||||
|
||||
// Add matching existing items
|
||||
suggestions = this.getMatchingSuggestions(map, term, context);
|
||||
|
||||
// Always add option to create new item
|
||||
// Always add the option to create a new item
|
||||
suggestions.push(this.createNewItemSuggestion(context));
|
||||
|
||||
return suggestions;
|
||||
|
|
@ -148,14 +163,17 @@ export class SuggestionProvider extends EditorSuggest<MentionSuggestion> {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (this.isMatch(key, term)) {
|
||||
suggestions.push({
|
||||
suggestionType: 'set',
|
||||
displayText: map[key].name.trim(),
|
||||
linkName: map[key].name,
|
||||
context,
|
||||
});
|
||||
if (!this.isMatch(key, term)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
suggestions.push({
|
||||
suggestionType: 'set',
|
||||
displayText: map[key].name.trim(),
|
||||
linkName: map[key].name,
|
||||
path: map[key].path,
|
||||
context,
|
||||
});
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
|
|
@ -167,9 +185,9 @@ export class SuggestionProvider extends EditorSuggest<MentionSuggestion> {
|
|||
private isMatch(key: string, term: string): boolean {
|
||||
if (this.settings.matchStart) {
|
||||
return key.startsWith(term);
|
||||
} else {
|
||||
return key.includes(term);
|
||||
}
|
||||
|
||||
return key.includes(term);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -180,6 +198,7 @@ export class SuggestionProvider extends EditorSuggest<MentionSuggestion> {
|
|||
suggestionType: 'create',
|
||||
displayText: context.query,
|
||||
linkName: context.query,
|
||||
path: '',
|
||||
context,
|
||||
};
|
||||
}
|
||||
|
|
@ -189,7 +208,7 @@ export class SuggestionProvider extends EditorSuggest<MentionSuggestion> {
|
|||
*/
|
||||
renderSuggestion(value: MentionSuggestion, el: HTMLElement): void {
|
||||
if (value.suggestionType === 'create') {
|
||||
const type = getTypeDef(this.settings.mentionTypes, this.currSign);
|
||||
const type = this.currType;
|
||||
const label = type?.label || 'Item';
|
||||
|
||||
el.setText(`Create ${label}: ${value.displayText}`);
|
||||
|
|
@ -198,14 +217,19 @@ export class SuggestionProvider extends EditorSuggest<MentionSuggestion> {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle selection of a suggestion
|
||||
*/
|
||||
selectSuggestion(value: MentionSuggestion, evt: MouseEvent | KeyboardEvent): void {
|
||||
const link = createMentionLink(this.currSign, value.linkName);
|
||||
const config = this.currType;
|
||||
if (!config?.sign) {
|
||||
return;
|
||||
}
|
||||
|
||||
const context = value.context;
|
||||
console.log('Insert:', config, value)
|
||||
const insertion = createMentionString(this.app, context.file.path, value.path, config.sign, value.linkName, config.type ?? 'link');
|
||||
this.prevMention = value.linkName;
|
||||
|
||||
value.context.editor.replaceRange(
|
||||
link,
|
||||
insertion,
|
||||
value.context.start,
|
||||
value.context.end,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { getLinkFromPath } from './link-utils';
|
|||
*/
|
||||
export class FileIndexer {
|
||||
private app: App;
|
||||
private settings: MentionSettings;
|
||||
private readonly settings: MentionSettings;
|
||||
private fileMaps: FileMaps = {};
|
||||
|
||||
constructor(app: App, settings: MentionSettings) {
|
||||
|
|
@ -26,11 +26,14 @@ export class FileIndexer {
|
|||
|
||||
// Process each file
|
||||
files.forEach(file => {
|
||||
if (file instanceof TFile && file.extension === 'md') {
|
||||
const mentionLink = getLinkFromPath(file.path, this.settings);
|
||||
if (mentionLink) {
|
||||
this.addFileToMap(mentionLink);
|
||||
}
|
||||
if (! (file instanceof TFile) || file.extension !== 'md') {
|
||||
return;
|
||||
}
|
||||
|
||||
const mentionLink = getLinkFromPath(file.path, this.settings);
|
||||
|
||||
if (mentionLink) {
|
||||
this.addFileToMap(mentionLink);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +1,4 @@
|
|||
import { App, TFile } from 'obsidian';
|
||||
import { MentionSettings, MentionType, MentionLink } from '../types';
|
||||
|
||||
/**
|
||||
|
|
@ -11,6 +12,11 @@ export function getLinkFromPath(path: string, settings: MentionSettings): Mentio
|
|||
return null;
|
||||
}
|
||||
|
||||
const basename = path.split('/')?.pop()?.replace(/\.[^.]+$/, '');
|
||||
if (!basename) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (let i = 0; i < settings.mentionTypes.length; i++) {
|
||||
const type = settings.mentionTypes[i];
|
||||
|
||||
|
|
@ -18,20 +24,15 @@ export function getLinkFromPath(path: string, settings: MentionSettings): Mentio
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!path.includes('/' + type.sign)) {
|
||||
if (!basename.startsWith(type.sign)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const regex = new RegExp(`/${type.sign}([^/]+)\\.md$`);
|
||||
let result = regex.exec(path);
|
||||
|
||||
if (result?.[1]) {
|
||||
return {
|
||||
sign: type.sign,
|
||||
name: result[1],
|
||||
path,
|
||||
};
|
||||
}
|
||||
return {
|
||||
sign: type.sign,
|
||||
name: basename.slice(type.sign.length).trim(),
|
||||
path,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
|
|
@ -55,10 +56,40 @@ export function getTypeDef(types: MentionType[], sign: string): MentionType | nu
|
|||
|
||||
/**
|
||||
* Create a link string for a mention
|
||||
* @param app
|
||||
* @param sourcePath The file with receives the link
|
||||
* @param linkTo Path to the linked file
|
||||
* @param sign The mention sign
|
||||
* @param name The name to link to
|
||||
* @param type Insertion type (link or text)
|
||||
* @returns Formatted link string
|
||||
*/
|
||||
export function createMentionLink(sign: string, name: string): string {
|
||||
return `[[${sign}${name}]]`;
|
||||
export function createMentionString(app: App, sourcePath: string, linkTo: string, sign: string, name: string, type: string): string {
|
||||
const basename = `${sign}${name}`;
|
||||
|
||||
if ('text' === type) {
|
||||
return basename;
|
||||
}
|
||||
|
||||
const file = app.vault.getAbstractFileByPath(linkTo);
|
||||
|
||||
if (!(file instanceof TFile)) {
|
||||
return basename;
|
||||
}
|
||||
|
||||
return app.fileManager.generateMarkdownLink(file, sourcePath, '', basename);
|
||||
}
|
||||
|
||||
export function generateLinkPreview(app: App, type: string, prefix: string, text: string): string {
|
||||
const useMarkdownLinks = (app.vault as any).getConfig('useMarkdownLinks');
|
||||
|
||||
if ('text' === type) {
|
||||
return text;
|
||||
}
|
||||
const link = encodeURIComponent(text);
|
||||
const label = prefix + text;
|
||||
|
||||
return useMarkdownLinks
|
||||
? `[${label}](${prefix}${link})`
|
||||
: `[[${label}]]`;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
import { App, PluginSettingTab, Setting } from 'obsidian';
|
||||
import { AvailableSigns } from '../types';
|
||||
import { ALLOWED_SIGNS } from '../constants';
|
||||
import MentionThingsPlugin from '../main';
|
||||
import { generateLinkPreview } from '../mention/link-utils';
|
||||
import { AvailableSigns, AvailableTypes } from '../types';
|
||||
|
||||
/**
|
||||
* Settings tab for the Mention Things plugin
|
||||
|
|
@ -62,6 +63,9 @@ export class SettingsTab extends PluginSettingTab {
|
|||
private renderMentionTypeRow(containerEl: HTMLElement, value: any, index: number, usedSigns: string[]): void {
|
||||
const setting = new Setting(containerEl);
|
||||
const availableSigns = this.getAvailableSigns(usedSigns, value?.sign);
|
||||
const availableTypes = this.getAvailableTypes();
|
||||
|
||||
setting.setClass('mention-type-row');
|
||||
|
||||
// Sign dropdown
|
||||
setting.addDropdown(
|
||||
|
|
@ -83,10 +87,33 @@ export class SettingsTab extends PluginSettingTab {
|
|||
.onChange(async (value) => {
|
||||
this.plugin.settings.mentionTypes[index].label = value;
|
||||
await this.plugin.saveSettings();
|
||||
updatePreview();
|
||||
})
|
||||
.inputEl.addClass('type_label'),
|
||||
.inputEl.addClass('type-label'),
|
||||
);
|
||||
|
||||
// Link style dropdown
|
||||
setting.addDropdown(
|
||||
list => list
|
||||
.addOptions(availableTypes)
|
||||
.setValue(value?.type || 'link')
|
||||
.onChange(async (value) => {
|
||||
this.plugin.settings.mentionTypes[index].type = value;
|
||||
await this.plugin.saveSettings();
|
||||
this.display();
|
||||
}),
|
||||
);
|
||||
|
||||
const updatePreview = (): void => {
|
||||
const label = value.label || 'Name';
|
||||
preview.classList.add('type-preview');
|
||||
preview.innerText = generateLinkPreview(this.app, value.type, value.sign, label);
|
||||
};
|
||||
|
||||
const preview = setting.controlEl.createSpan();
|
||||
setting.controlEl.append(preview);
|
||||
updatePreview();
|
||||
|
||||
// Delete button
|
||||
setting.addExtraButton(
|
||||
button => button
|
||||
|
|
@ -146,8 +173,7 @@ export class SettingsTab extends PluginSettingTab {
|
|||
.setValue(this.plugin.settings.stopCharacters ?? '?!"\'`:;/#+*=&%$§<>')
|
||||
.onChange(async (value) => {
|
||||
// Sanitize: remove duplicate characters
|
||||
const uniqueChars = [...new Set(value.split(''))].join('');
|
||||
this.plugin.settings.stopCharacters = uniqueChars;
|
||||
this.plugin.settings.stopCharacters = [...new Set(value.split(''))].join('');
|
||||
await this.plugin.saveSettings();
|
||||
});
|
||||
});
|
||||
|
|
@ -170,4 +196,13 @@ export class SettingsTab extends PluginSettingTab {
|
|||
|
||||
return availableSigns;
|
||||
}
|
||||
|
||||
private getAvailableTypes(): AvailableTypes {
|
||||
const availableTypes: AvailableTypes = {};
|
||||
|
||||
availableTypes.text = 'As Text';
|
||||
availableTypes.link = 'As Link';
|
||||
|
||||
return availableTypes;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,20 @@
|
|||
.mention-type-row .type-label {
|
||||
flex: 1 1 auto;
|
||||
.mention-type-row {
|
||||
--preview-width: 20ch;
|
||||
|
||||
.type-label {
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.type-preview {
|
||||
display: inline-block;
|
||||
min-width: var(--preview-width);
|
||||
max-width: var(--preview-width);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
|
||||
text-align: left;
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export interface MentionSettings {
|
|||
* Represents a type of mention with its sign and label
|
||||
*/
|
||||
export interface MentionType {
|
||||
type?: string;
|
||||
sign?: string;
|
||||
label?: string;
|
||||
}
|
||||
|
|
@ -43,6 +44,10 @@ export interface AvailableSigns {
|
|||
[sign: string]: string;
|
||||
}
|
||||
|
||||
export interface AvailableTypes {
|
||||
[type: string]: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Suggestion item structure
|
||||
*/
|
||||
|
|
@ -50,6 +55,7 @@ export interface MentionSuggestion {
|
|||
suggestionType: 'set' | 'create';
|
||||
displayText: string;
|
||||
linkName: string;
|
||||
path: string;
|
||||
context: any;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue