-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
init: this is working pretty well so far
- Loading branch information
0 parents
commit ebc86de
Showing
7 changed files
with
298 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
root = true | ||
|
||
[*] | ||
charset = utf-8 | ||
indent_style = tab | ||
indent_size = 4 | ||
|
||
[*.js] | ||
insert_final_newline = true | ||
trim_trailing_whitespace = true | ||
|
||
[*.md] | ||
trim_trailing_whitespace = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Very Open License (VOL) | ||
|
||
The contributor(s) to this creative work voluntarily grant permission | ||
to any individual(s) or entities of any kind | ||
- to use the creative work in any manner, | ||
- to modify the creative work without restriction, | ||
- to sell the creative work or derivatives thereof for profit, and | ||
- to release modifications of the creative work in part or whole under any license | ||
with no requirement for compensation or recognition of any kind. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
# Obsidian `@People` | ||
|
||
Obsidian plugin to add that familiar @-to-tag-someone syntax: | ||
|
||
![](./example.png) | ||
|
||
When you hit enter on a suggestion, it'll create a link that looks like this: | ||
|
||
``` | ||
The author was [[@Rich Hickey]] | ||
``` | ||
|
||
and leave the cursor at the end. | ||
|
||
## Options | ||
|
||
There's not a lot to configure here, but they are important: | ||
|
||
### 1. Where are the people files? | ||
|
||
You probably want to group the people files in a folder. | ||
|
||
I usually do something like this: | ||
|
||
``` | ||
People/ | ||
@Rich Hickey.md | ||
@Rich Harris.md | ||
``` | ||
|
||
You can configure that in settings to point to somewhere else, like `Reference/People/` or whatever makes sense. | ||
|
||
### 2. Explicit link structure? | ||
|
||
By default, the plugin will insert the simple version: | ||
|
||
``` | ||
[[@Rich Hickey]] | ||
``` | ||
|
||
But you might rather make that explicit, in which case you can enable "explicit links" and they'll look like this instead: | ||
|
||
``` | ||
[[People/@Rich Hickey.md|@Rich Hickey]] | ||
``` | ||
|
||
### 3. Last name grouping? | ||
|
||
For my personal Obsidian vaults, I have a lot of people with my same last name, so I put them in sub-folders for organization. | ||
|
||
You can toggle the "last name folder" option, and it'll do that in the links. | ||
|
||
The earlier example folder structure would be: | ||
|
||
``` | ||
People/ | ||
Hickey/ | ||
@Rich Hickey.md | ||
Harris/ | ||
@Rich Harris.md | ||
``` | ||
|
||
And then the inserted link would look like: | ||
|
||
``` | ||
[[People/Hickey/@Rich Hickey.md|@Rich Hickey]] | ||
``` | ||
|
||
> Note: figuring out what the "last name" is (or if it even has one) is really complicated! This plugin takes a very simply approach: if you split a name by the space character, it'll just pick the last "word". So for example "Charles Le Fabre" would be "Fabre" and *not* "Le Fabre". | ||
> | ||
> I'm open to better implementations that don't add a lot of complexity, just start a discussion. | ||
## License | ||
|
||
Published and made available freely under the [Very Open License](http://veryopenlicense.com/). |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,167 @@ | ||
const { App, Editor, EditorSuggest, TFile, Notice, Plugin, PluginSettingTab, Setting } = require('obsidian') | ||
|
||
const DEFAULT_SETTINGS = { | ||
peopleFolder: 'People/', | ||
// Defaults: | ||
// useExplicitLinks: undefined, | ||
// useLastNameFolder: undefined, | ||
} | ||
|
||
const NAME_REGEX = /\/@([^\/]+)\.md$/ | ||
const LAST_NAME_REGEX = /([\S]+)$/ | ||
|
||
const getPersonName = (filename, settings) => filename.startsWith(settings.peopleFolder) | ||
&& filename.endsWith('.md') | ||
&& filename.includes('/@') | ||
&& NAME_REGEX.exec(filename)?.[1] | ||
|
||
module.exports = class AtPeople extends Plugin { | ||
async onload() { | ||
await this.loadSettings() | ||
this.registerEvent(this.app.vault.on('delete', async event => { await this.update(event) })) | ||
this.registerEvent(this.app.vault.on('create', async event => { await this.update(event) })) | ||
this.registerEvent(this.app.vault.on('rename', async (event, originalFilepath) => { await this.update(event, originalFilepath) })) | ||
this.app.workspace.onLayoutReady(this.initialize) | ||
this.addSettingTab(new AtPeopleSettingTab(this.app, this)) | ||
this.suggestor = new AtPeopleSuggestor(this.app, this.settings) | ||
this.registerEditorSuggest(this.suggestor) | ||
} | ||
|
||
async loadSettings() { | ||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()) | ||
} | ||
|
||
async saveSettings() { | ||
await this.saveData(this.settings || DEFAULT_SETTINGS) | ||
} | ||
|
||
updatePeopleMap = () => { | ||
this.suggestor.updatePeopleMap(this.peopleFileMap) | ||
} | ||
|
||
update = async ({ path, deleted, ...remaining }, originalFilepath) => { | ||
this.peopleFileMap = this.peopleFileMap || {} | ||
const name = getPersonName(path, this.settings) | ||
let needsUpdated | ||
if (name) { | ||
this.peopleFileMap[name] = path | ||
needsUpdated = true | ||
} | ||
originalFilepath = originalFilepath && getPersonName(originalFilepath, this.settings) | ||
if (originalFilepath) { | ||
delete this.peopleFileMap[originalFilepath] | ||
needsUpdated = true | ||
} | ||
if (needsUpdated) this.updatePeopleMap() | ||
} | ||
|
||
initialize = () => { | ||
this.peopleFileMap = {} | ||
for (const filename in this.app.vault.fileMap) { | ||
const name = getPersonName(filename, this.settings) | ||
if (name) this.peopleFileMap[name] = filename | ||
} | ||
this.updatePeopleMap() | ||
} | ||
} | ||
|
||
class AtPeopleSuggestor extends EditorSuggest { | ||
constructor(app, settings) { | ||
super(app) | ||
this.settings = settings | ||
} | ||
updatePeopleMap(peopleFileMap) { | ||
this.peopleFileMap = peopleFileMap | ||
} | ||
onTrigger(cursor, editor, tFile) { | ||
let charsLeftOfCursor = editor.getLine(cursor.line).substring(0, cursor.ch) | ||
let atIndex = charsLeftOfCursor.lastIndexOf('@') | ||
let query = atIndex >= 0 && charsLeftOfCursor.substring(atIndex + 1) | ||
if (query && !query.includes(']]')) { | ||
return { | ||
start: { line: cursor.line, ch: atIndex }, | ||
end: { line: cursor.line, ch: cursor.ch }, | ||
query, | ||
} | ||
} | ||
return null | ||
} | ||
getSuggestions(context) { | ||
let suggestions = [] | ||
for (let key in (this.peopleFileMap || {})) | ||
if (key.toLowerCase().startsWith(context.query)) | ||
suggestions.push({ | ||
suggestionType: 'set', | ||
displayText: key, | ||
context, | ||
}) | ||
suggestions.push({ | ||
suggestionType: 'create', | ||
displayText: context.query, | ||
context, | ||
}) | ||
return suggestions | ||
} | ||
renderSuggestion(value, elem) { | ||
if (value.suggestionType === 'create') elem.setText('New person: ' + value.displayText) | ||
else elem.setText(value.displayText) | ||
} | ||
selectSuggestion(value) { | ||
let link | ||
if (this.settings.useExplicitLinks && this.settings.useLastNameFolder) { | ||
let lastName = LAST_NAME_REGEX.exec(value.displayText) | ||
lastName = lastName && lastName[1] && (lastName[1] + '/') || '' | ||
link = `[[${this.settings.peopleFolder}${lastName}@${value.displayText}.md|@${value.displayText}]]` | ||
} else if (this.settings.useExplicitLinks && !this.settings.useLastNameFolder) { | ||
link = `[[${this.settings.peopleFolder}@${value.displayText}.md|@${value.displayText}]]` | ||
} else { | ||
link = `[[@${value.displayText}]]` | ||
} | ||
value.context.editor.replaceRange( | ||
link, | ||
value.context.start, | ||
value.context.end, | ||
) | ||
} | ||
} | ||
|
||
class AtPeopleSettingTab extends PluginSettingTab { | ||
constructor(app, plugin) { | ||
super(app, plugin) | ||
this.plugin = plugin | ||
} | ||
display() { | ||
const { containerEl } = this | ||
containerEl.empty() | ||
new Setting(containerEl) | ||
.setName('People folder') | ||
.setDesc('The folder where people files live, e.g. "People/". (With trailing slash.)') | ||
.addText( | ||
text => text | ||
.setPlaceholder(DEFAULT_SETTINGS.peopleFolder) | ||
.setValue(this.plugin.settings.peopleFolder) | ||
.onChange(async (value) => { | ||
this.plugin.settings.peopleFolder = value | ||
await this.plugin.saveSettings() | ||
}) | ||
) | ||
new Setting(containerEl) | ||
.setName('Explicit links') | ||
.setDesc('When inserting links include the full path, e.g. [[People/@Bob Dole.md|@Bob Dole]]') | ||
.addToggle( | ||
toggle => toggle.onChange(async (value) => { | ||
this.plugin.settings.useExplicitLinks = value | ||
await this.plugin.saveSettings() | ||
}) | ||
) | ||
new Setting(containerEl) | ||
.setName('Last name folder') | ||
.setDesc('When using explicit links, use the "last name" (the last non-spaced word) as a sub-folder, e.g. [[People/Dole/@Bob Dole.md|@Bob Dole]]') | ||
.addToggle( | ||
toggle => toggle.onChange(async (value) => { | ||
this.plugin.settings.useLastNameFolder = value | ||
await this.plugin.saveSettings() | ||
}) | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
{ | ||
"id": "at-people", | ||
"name": "At People", | ||
"version": "1.0.0", | ||
"description": "Use the @ to create links to people files.", | ||
"author": "saibotsivad", | ||
"authorUrl": "https://davistobias.com", | ||
"isDesktopOnly": false | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"name": "obsidian-at-people", | ||
"version": "1.0.0", | ||
"description": "Use the @ to create links to people files.", | ||
"main": "main.js", | ||
"repository": { | ||
"type": "git", | ||
"url": "git+https://github.com/saibotsivad/obsidian-at-people.git" | ||
}, | ||
"keywords": [ | ||
"obsidian", | ||
"obsidian-plugin", | ||
"people" | ||
], | ||
"author": { | ||
"name": "Tobias Davis", | ||
"email": "[email protected]", | ||
"url": "https://davistobias.com" | ||
}, | ||
"license": "See license in LICENSE.md", | ||
"bugs": { | ||
"url": "https://github.com/saibotsivad/obsidian-at-people/issues" | ||
}, | ||
"homepage": "https://github.com/saibotsivad/obsidian-at-people#readme" | ||
} |