Skip to content

Commit

Permalink
init: this is working pretty well so far
Browse files Browse the repository at this point in the history
  • Loading branch information
saibotsivad committed Aug 17, 2023
0 parents commit ebc86de
Show file tree
Hide file tree
Showing 7 changed files with 298 additions and 0 deletions.
13 changes: 13 additions & 0 deletions .editorconfig
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
9 changes: 9 additions & 0 deletions LICENSE.md
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.
75 changes: 75 additions & 0 deletions README.md
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/).
Binary file added example.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
167 changes: 167 additions & 0 deletions main.js
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()
})
)
}
}
9 changes: 9 additions & 0 deletions manifest.json
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
}
25 changes: 25 additions & 0 deletions package.json
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"
}

0 comments on commit ebc86de

Please sign in to comment.