Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added automatic re-indexing #19

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
93 changes: 6 additions & 87 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ This hotkey lets you:
- Places your cursor so you can fill in the details quickly
- Jump from your footnote TO the footnote detail
- Jump from your footnote detail BACK to the footnote
- New in January 2022: Automatic re-indexing of footnotes

![Overview](https://github.com/akaalias/obsidian-footnotes/blob/master/basic.gif?raw=true)

Expand Down Expand Up @@ -36,6 +37,9 @@ I personally use <kbd>Command</kbd>+<kbd>Shift</kbd>+<kbd>6</kbd> because "6" on
- And a new footnote details marker (e.g. `[^2]: `) is inserted on the last line of the document
- And my cursor is now placed at the end of the detail marker (e.g. `[^2]: ▊`)

### Scenario: Footnote is inserted in between existing footnotes
- All existing footnotes get updated and the new one inserted as in the previous Scenarios

### Scenario: Jumping TO a footnote detail
- Given I'm on a footnote detail line (e.g. `[^1]: ▊`)
- When I hit `my footnote hotkey`
Expand All @@ -47,93 +51,8 @@ I personally use <kbd>Command</kbd>+<kbd>Shift</kbd>+<kbd>6</kbd> because "6" on
- Then my cursor is placed to the right of the footnote (e.g. `[^1]: ▊`)

### Known Limitations or Untested Scenarios
#### Indices are not updated
Inserting new footnote in-between two existing footnotes will insert the next numeric index (e.g. `1, 3, 2`).

It will NOT update the indices according to their natural order (e.g. `1, 2, 3`).

```markdown
Example sentence[^1] with two▊ footnotes[^2] already.

[^1]: Foo
[^2]: Bar
```

After insertion:

```markdown
Example sentence[^1] with two[^3] footnotes[^2] already.

[^1]: Foo
[^2]: Bar
[^3]: Baz
```

See "Automatically Re-Index Footnotes" below for a proposed feature

## Future Possible Feature Ideas
### Automatically Re-Index Footnotes
Re-index and re-sort all footnotes when you insert a new one in-between one or more existing numbered footnotes:

```markdown
Example sentence[^1] with two▊ footnotes[^2] already.

[^1]: Foo
[^2]: Bar
```
#### Base Scenario
- Given there are two footnotes already
- When I enter a new footnote in-between those two
- Then the NEW footnote gets the index "2"
- And the previously second footnote gets the index "3"
- And the NEW footnote detail is inserted as the second entry at the bottom
- And the previously second footnote detail at the bottom is updated to be "3"
- And the previously second footnote detail at the bottom is updated to be in third position

```markdown
Example sentence[^1] with two[^2] footnotes[^3] already.

[^1]: Foo
[^2]: Baz
[^3]: Bar▊
```

#### Edge Cases to consider ("What if...?")
##### What if... new footnote is inserted before the first footnote?
```markdown
Some sentence▊ with existing note[^1]

[^1]: Details
```
##### What if... text has the same footnote at several places?
```markdown
Some sentence with existing note[^1] and the same▊ footnote re-appears later[^1].


[^1]: Details
```
##### What if...Footnote details are spread across the text?
```markdown
Some sentence with existing note[^1] some more text▊

[^1]: Inline footnote details

Another text part▊
```
##### What if... the footnote details are multi-line on the bottom?
```markdown
Some sentence with existing note[^1] some more text▊

[^1]: The details that
Span across
Multiple lines
```
##### What if... there are non-numeric footnotes in the text?
```markdown
Some sentence with existing note[^✝] some more text▊

[^✝]: Details
```
#### Manually Deleting Footnotes or Copy-Pasting Text Chunks
Because the plugin only gets triggered when it is manually invoked for example by pressing the hotkey, this is an unsolved issue.

## Background
This plugin is based on the great idea by [jacob.4ristotle](https://forum.obsidian.md/u/jacob.4ristotle/summary) posted in the ["Footnote Shortcut"](https://forum.obsidian.md/t/footnote-shortcut/8872) thread.
Expand Down
67 changes: 40 additions & 27 deletions main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { MarkdownView, Plugin } from 'obsidian';
export default class MyPlugin extends Plugin {

private detailLineRegex = /\[\^(\d+)\]\:/;
private reOnlyDetails = /\[\^(\d+)\]\:/gi;
private reOnlyMarkers = /\[\^(\d+)\]/gi;
private numericalRe = /(\d+)/

Expand All @@ -27,12 +28,11 @@ export default class MyPlugin extends Plugin {
const doc = mdView.sourceMode.cmEditor;
const cursorPosition = doc.getCursor();
const lineText = doc.getLine(cursorPosition.line);
const markdownText = mdView.data;

if (this.shouldJumpFromDetailToMarker(lineText, cursorPosition, doc)) return;
if (this.shouldJumpFromMarkerToDetail(lineText, cursorPosition, doc)) return;

return this.shouldCreateNewFootnote(lineText, cursorPosition, doc, markdownText);
return this.shouldCreateNewFootnote(lineText, cursorPosition, doc);
}

private shouldJumpFromDetailToMarker(lineText: string, cursorPosition: CodeMirror.Position, doc: CodeMirror.Editor) {
Expand Down Expand Up @@ -116,43 +116,56 @@ export default class MyPlugin extends Plugin {
return false;
}

private shouldCreateNewFootnote(lineText: string, cursorPosition: CodeMirror.Position, doc: CodeMirror.Editor, markdownText: string) {
private shouldCreateNewFootnote(lineText: string, cursorPosition: CodeMirror.Position, doc: CodeMirror.Editor) {
// create new footnote with the next numerical index
let matches = markdownText.match(this.reOnlyMarkers);
let numbers: Array<number> = [];
let currentMax = 1;
let match

if (matches != null) {
for (let i = 0; i <= matches.length - 1; i++) {
let match = matches[i];
match = match.replace("[^", "");
match = match.replace("]", "");
let matchNumber = Number(match);
numbers[i] = matchNumber;
if (matchNumber + 1 > currentMax) {
currentMax = matchNumber + 1;
}
// search highest footnote ID of footnotes before the cursor position
let beforeCursor = doc.getRange({line: 0, ch: 0}, doc.getCursor());

while ((match = this.reOnlyMarkers.exec(beforeCursor)) !== null) {
if (Number(match[1]) + 1 > currentMax) {
currentMax = Number(match[1]) + 1;
}
}

let footNoteId = currentMax;
let footnoteMarker = `[^${footNoteId}]`;
let linePart1 = lineText.substr(0, cursorPosition.ch)
let linePart2 = lineText.substr(cursorPosition.ch);
let newLine = linePart1 + footnoteMarker + linePart2

doc.replaceRange(newLine, {line: cursorPosition.line, ch: 0}, {line: cursorPosition.line, ch: lineText.length})
// increment footnote IDs after the cursor position
// only IDs bigger or equal to the new ID will be incremented in case an earlier one is used more than once
// this also includes the footnote details which is fine since we need to increment those anyways
let afterCursor = doc.getRange(doc.getCursor(), {line: doc.lastLine(), ch: doc.getLine(doc.lastLine()).length});

let lastLine = doc.getLine(doc.lineCount() - 1);
while ((match = this.reOnlyMarkers.exec(afterCursor)) !== null) {
if (Number(match[1]) >= footNoteId) {
const p = doc.offsetToPos(beforeCursor.length+match.index);
doc.replaceRange(String(Number(match[1])+1), {line: p.line, ch: p.ch + 2}, {line: p.line, ch: p.ch + 3});
}
}

// add new footnote marker
let footnoteMarker = `[^${footNoteId}]`;
doc.replaceRange(footnoteMarker, doc.getCursor())

// add new footnote detail
// search for the correct place first
let footnoteDetail = `[^${footNoteId}]: `;

if (lastLine.length > 0) {
doc.replaceRange("\n" + footnoteDetail, {line: doc.lineCount(), ch: 0})
} else {
doc.replaceRange(footnoteDetail, {line: doc.lineCount(), ch: 0})
while ((match = this.reOnlyDetails.exec(afterCursor)) !== null) {
if (Number(match[1]) == footNoteId) {
const p = doc.offsetToPos(beforeCursor.length + match.index + footnoteMarker.length);
doc.replaceRange(footnoteDetail + '\n', {line: p.line, ch: 0});
doc.setCursor({line: p.line, ch: footnoteDetail.length});
return;
}
}

doc.setCursor({line: doc.lineCount(), ch: footnoteDetail.length});
// if no footnote details where found just add a line to the bottom of the document
if (footNoteId == 1) {
// this is just for aesthetics
doc.replaceRange('\n', {line: doc.lastLine(), ch: doc.getLine(doc.lastLine()).length});
}
doc.replaceRange('\n' + footnoteDetail, {line: doc.lastLine(), ch: doc.getLine(doc.lastLine()).length});
doc.setCursor({line: doc.lastLine(), ch: footnoteDetail.length});
}
}