Skip to content

Commit

Permalink
add render
Browse files Browse the repository at this point in the history
  • Loading branch information
Ebonsignori committed Sep 9, 2023
1 parent 5ff2c5c commit 90d938b
Show file tree
Hide file tree
Showing 9 changed files with 229 additions and 69 deletions.
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,23 @@
# Obsidian Timeline Schedule

Inline timelines generated from human-readable todos e.g. 'Walk dog (30min)' in a ```schedule codeblock for [Obsidian](https://obsidian.md/)
![Time Schedule example](./docs/time-schedule-demo-1.0.0.gif)

Inline timelines generated from human-readable time strings, e.g. 'Walk dog (30min)' in a ```schedule codeblock. For [Obsidian](https://obsidian.md).

## Styling

See [styles.css](./styles.css) for a list of classes you can override.

## Installing

Search "Timeline Schedule" via the [built-in community plugin browser](https://help.obsidian.md/Extending+Obsidian/Community+plugins) in Obsidian.

## Contributing
## Contributing

Please [open an issue](https://github.com/Ebonsignori/obsidian-timeline-schedule/issues/new) with any suggestions or bug reports.

See [developer docs](docs/development.md) if you'd like to open a PR.
See [developer docs](docs/development.md) if you'd like to open a PR.

## Acknowledgements

[The Obsidian team](https://obsidian.md/about) for creating a wonderful product :purple_heart:

The implementation borrows from:

-
Binary file added docs/time-schedule-demo-1.0.0.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"name": "Timeline Schedule",
"version": "1.0.0",
"minAppVersion": "0.15.0",
"description": "Inline timelines generated from human-readable time strings in todos, e.g. 'Walk dog (30min)' in a ```schedule codeblock.",
"description": "Inline timelines generated from human-readable time strings, e.g. 'Walk dog (30min)' in a ```schedule codeblock.",
"author": "Evan Bonsignori",
"authorUrl": "https://github.com/Ebonsignori",
"isDesktopOnly": false
Expand Down
2 changes: 2 additions & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const matchBlockRegex = /^(\[.*\]:)(.*)/gi;
export const matchHumanTimeRegex = /\(.*?\)(\s*?)$/gi;
107 changes: 65 additions & 42 deletions src/extension-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,11 @@ import { ViewPlugin } from "@codemirror/view";
import type { PluginValue, EditorView } from "@codemirror/view";
import { moment, type App, type EditorPosition, MarkdownView } from "obsidian";
import timestring from "timestring";
import { DEFAULT_SETTINGS, TimelineScheduleSettings } from "src/settings/settings";

const startName = "[start]:";
const endName = "[end]:";
const matchBlockRegex = /^(\[.*\]:)(.*)/gi;
const matchHumanTimeRegex = /\(.*?\)(\s*?)$/gi;
import {
DEFAULT_SETTINGS,
TimelineScheduleSettings,
} from "src/settings/settings";
import { matchBlockRegex, matchHumanTimeRegex } from "./constants";

class TimelineScheduleExtension implements PluginValue {
private readonly view: EditorView;
Expand All @@ -18,6 +17,8 @@ class TimelineScheduleExtension implements PluginValue {

// Set from settings
private blockVariableName: string;
private startBlockName: string;
private endBlockName: string;
private shouldAppendEmptyTimeBlock: boolean;
private startDateFormat: string;
private endDateFormat: string;
Expand All @@ -34,17 +35,30 @@ class TimelineScheduleExtension implements PluginValue {
this.settings = settings;

this.codeBlockRegex = new RegExp(
"```(" +
"`{3}(" +
this.settings.blockVariableName +
"\\n?)([a-z\\s]*\\n[\\s\\S]*?)\\n```",
"\\s*?)\\n([\\w\\s\\S]*?)\\n`{3}",
"gim"
);

this.blockVariableName = this.settings.blockVariableName || DEFAULT_SETTINGS.blockVariableName;
this.shouldAppendEmptyTimeBlock = this.settings.shouldAppendEmptyTimeBlock || DEFAULT_SETTINGS.shouldAppendEmptyTimeBlock;
this.startDateFormat = this.settings.startDateFormat || DEFAULT_SETTINGS.startDateFormat;
this.endDateFormat = this.settings.endDateFormat || DEFAULT_SETTINGS.endDateFormat;
this.eventDateFormat = this.settings.eventDateFormat || DEFAULT_SETTINGS.eventDateFormat;
this.blockVariableName =
this.settings.blockVariableName ||
DEFAULT_SETTINGS.blockVariableName;
this.startBlockName = `[${
this.settings.startBlockName || DEFAULT_SETTINGS.startBlockName
}]:`;
this.endBlockName = `[${
this.settings.endBlockName || DEFAULT_SETTINGS.endBlockName
}]:`;
this.shouldAppendEmptyTimeBlock =
this.settings.shouldAppendEmptyTimeBlock ||
DEFAULT_SETTINGS.shouldAppendEmptyTimeBlock;
this.startDateFormat =
this.settings.startDateFormat || DEFAULT_SETTINGS.startDateFormat;
this.endDateFormat =
this.settings.endDateFormat || DEFAULT_SETTINGS.endDateFormat;
this.eventDateFormat =
this.settings.eventDateFormat || DEFAULT_SETTINGS.eventDateFormat;
}

public destroy(): void {
Expand All @@ -62,7 +76,7 @@ class TimelineScheduleExtension implements PluginValue {

// We found a match with `schedule` as code block title
for (const match of matches) {
if (!match.index) {
if (typeof match.index === "undefined" || match.index === null) {
continue;
}
if (match?.[1].trim() !== this.blockVariableName) {
Expand All @@ -80,7 +94,7 @@ class TimelineScheduleExtension implements PluginValue {
<number>match.index + 4 + this.blockVariableName.length;
let hasChanges = false;

const innerContents = match[2];
const innerContents = match?.[2];
const splitLines = innerContents.trim().split("\n");

// Fill in the codeblock with empty lines so we can replace them with 3 core time blocks in the for loop
Expand Down Expand Up @@ -108,36 +122,39 @@ class TimelineScheduleExtension implements PluginValue {
const textAfterColon = (<string>lineMatches?.[2] || "").trim();

// add [start] block if first line and it's missing
if (i === 0 && textBlock !== startName) {
if (i === 0 && textBlock !== this.startBlockName) {
if (textAfterColon) {
startTime = moment(textAfterColon, this.startDateFormat);
line = `${startName} ${startTime.format(this.startDateFormat)}`;
line = `${this.startBlockName} ${startTime.format(
this.startDateFormat
)}`;
} else {
startTime = moment();
line = `${startName} ${startTime.format(this.startDateFormat)}`;
line = `${this.startBlockName} ${startTime.format(
this.startDateFormat
)}`;
}

// Update line
hasChanges = true;
splitLines[i] = line;
continue;
}

// Determine start time if not initialized from creating a [start] block
if (textBlock === startName) {
} else if (i === 0 && textBlock === this.startBlockName) {
// Determine start time if not initialized from creating a [start] block
if (textAfterColon) {
startTime = moment(textAfterColon, this.startDateFormat);
}
continue;
}

// Replace all [time] blocks or empty lines with the correct time
if (
!textBlock ||
(textBlock !== startName && textBlock !== endName)
) {
if (!textBlock || textBlock !== this.endBlockName) {
// If empty line and we have appendEmptyTimeBlock enabled, delete the empty line
if (!textBlock && this.shouldAppendEmptyTimeBlock) {
if (
!textBlock &&
this.shouldAppendEmptyTimeBlock &&
i < splitLines.length - 1
) {
const nextLineMatches = [
...splitLines[i + 1].matchAll(matchBlockRegex),
]?.[0];
Expand All @@ -154,7 +171,9 @@ class TimelineScheduleExtension implements PluginValue {
if (elapsedMs) {
nextDate = nextDate.add(elapsedMs, "millisecond");
}
const timeBlockString = `[${nextDate.format(this.eventDateFormat)}]:`;
const timeBlockString = `[${nextDate.format(
this.eventDateFormat
)}]:`;
if (textBlock !== timeBlockString) {
line = `${timeBlockString} ${textAfterColon}`;
// Update line
Expand All @@ -164,7 +183,7 @@ class TimelineScheduleExtension implements PluginValue {
}

// Use [time] blocks to calculate elapsed time
if (textBlock !== endName) {
if (textBlock !== this.endBlockName) {
// Existing time block
const humanTime = textAfterColon.match(matchHumanTimeRegex);
if (humanTime) {
Expand All @@ -185,7 +204,7 @@ class TimelineScheduleExtension implements PluginValue {
if (elapsedMs) {
endDate = endDate.add(elapsedMs, "millisecond");
}
const endBlockString = `${endName} ${endDate.format(
const endBlockString = `${this.endBlockName} ${endDate.format(
this.endDateFormat
)}`;
if (line.trim() !== endBlockString) {
Expand Down Expand Up @@ -220,22 +239,26 @@ class TimelineScheduleExtension implements PluginValue {
this.app?.workspace?.getActiveViewOfType(MarkdownView);
const existingScroll = activeView?.currentMode?.getScroll();
const existingCursor = this.getCursor();
this.view.dispatch(
this.view.state.update({
changes: {
from: innerIndex,
to: innerIndex + innerContents.length - 1,
insert: newContents,
},
})
);
const changes = {
from: innerIndex,
insert: newContents,
};
if (innerContents.trim()) {
// @ts-expect-error .to is added
changes.to = innerIndex + innerContents.length;
}
this.view.dispatch(this.view.state.update({ changes }));
const newCursorLine = this.view.state.doc.toString().split("\n")[
existingCursor.line - 1
];
let bumpCursor = 0;
const matches = [...newCursorLine.matchAll(matchBlockRegex)]?.[0];
if (matches?.[2].trim() === "") {
bumpCursor = newCursorLine.length;
if (newCursorLine) {
const matches = [
...newCursorLine.matchAll(matchBlockRegex),
]?.[0];
if (matches?.[2].trim() === "") {
bumpCursor = newCursorLine.length;
}
}
if (existingCursor?.ch) {
this.view.dispatch(
Expand Down
21 changes: 11 additions & 10 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from "./settings/settings";
import { editorExtension } from "./extension-handler";
import { EditorView } from "@codemirror/view";
import { PrettyRender } from "./pretty-render";

export default class TimelineSchedule extends Plugin {
settings: TimelineScheduleSettings;
Expand All @@ -16,16 +17,16 @@ export default class TimelineSchedule extends Plugin {

this.addSettingTab(new SettingsTab(this.app, this));

this.registerMarkdownCodeBlockProcessor(
this.settings.blockVariableName,
(source, el, ctx) => {
console.log("Here in CodeBlock", source, el, ctx);
}
);

this.registerMarkdownPostProcessor((el, ctx) => {
console.log("Here in PostProcessor", el, ctx);
});
if (this.settings.renderInPreviewMode) {
this.registerMarkdownCodeBlockProcessor(
this.settings.blockVariableName,
(source, el, ctx) => {
if (source.trim()) {
ctx.addChild(new PrettyRender(el, source));
}
}
);
}

if (this.settings.enableCodeblockTextCompletion) {
this.registerEditorExtension(editorExtension);
Expand Down
55 changes: 55 additions & 0 deletions src/pretty-render.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { MarkdownRenderChild } from "obsidian";
import { matchBlockRegex } from "./constants";

export class PrettyRender extends MarkdownRenderChild {
body: string;

constructor(containerEl: HTMLElement, body: string) {
super(containerEl);

this.body = body;
}

onload() {
const lines = this.body.trim().split("\n");
const listEl = this.containerEl.createEl("ul", {
cls: "timeline-schedule-list",
});
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const [, block, contents] =
[...line.matchAll(matchBlockRegex)]?.[0] || [];

if (block && contents.trim()) {
let listItemClassName = "timeline-schedule-list-item";
if (i === 0) {
listItemClassName += " timeline-schedule-start";
} else if (i === lines.length - 1) {
listItemClassName += " timeline-schedule-end";
}

const listItem = this.containerEl.createEl("li", {
cls: listItemClassName,
});

listItem.appendChild(
this.containerEl.createEl("span", {
cls: "timeline-schedule-list-item-block",
text: block
.replace(/[[\]]/gi, "")
.trim()
.replace(/:$/, ""),
})
);
listItem.appendChild(
this.containerEl.createEl("span", {
cls: "timeline-schedule-list-item-contents",
text: contents,
})
);
listEl.appendChild(listItem);
}
}
this.containerEl.appendChild(listEl);
}
}
Loading

0 comments on commit 90d938b

Please sign in to comment.