Skip to content

Commit 7146dfb

Browse files
committed
Merge branch 'community-pr'
2 parents fe67cbb + 509f726 commit 7146dfb

File tree

7 files changed

+179
-262
lines changed

7 files changed

+179
-262
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,3 +102,6 @@ dist
102102

103103
# TernJS port file
104104
.tern-port
105+
106+
# ignore plugin build
107+
main.js

main.js

Lines changed: 0 additions & 162 deletions
This file was deleted.

main.ts

Lines changed: 146 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -1,148 +1,210 @@
11
import {
22
MarkdownPostProcessor,
33
MarkdownPostProcessorContext,
4-
MarkdownPreviewRenderer,
54
Plugin,
65
} from "obsidian";
76

87
enum TaskType {
9-
TODO,
10-
DONE,
11-
DOING,
12-
LATER,
13-
CANCELED,
14-
UNKNOWN,
8+
TODO = "TODO",
9+
DONE = "DONE",
10+
DOING = "DOING",
11+
LATER = "LATER",
12+
CANCELED = "CANCELED",
13+
UNKNOWN = "UNKNOWN",
1514
}
1615

17-
const HEADING_REGEX = {
18-
h1: /(?:\s+)?- # (?:.*)$/gms,
19-
h2: /(?:\s+)?- ## (?:.*)$/gms,
20-
h3: /(?:\s+)?- ### (?:.*)$/gms,
21-
h4: /(?:\s+)?- #### (?:.*)$/gms,
22-
h5: /(?:\s+)?- ##### (?:.*)$/gms,
23-
};
24-
25-
const VERSION = "0.0.3";
26-
27-
function parseTaskType(content: string): TaskType | null {
28-
if (content.startsWith("DONE ")) {
29-
return TaskType.DONE;
30-
} else if (content.startsWith("TODO ")) {
31-
return TaskType.TODO;
32-
} else if (content.startsWith("DOING ")) {
33-
return TaskType.DOING;
34-
} else if (content.startsWith("LATER ")) {
35-
return TaskType.LATER;
36-
} else if (content.startsWith("CANCELED ")) {
37-
return TaskType.CANCELED;
38-
} else {
39-
return TaskType.UNKNOWN;
40-
}
16+
enum TaskCSSClass {
17+
COMPLETE = "logseq-complete-task",
18+
INCOMPLETE = "logseq-incomplete-task",
19+
KEYWORD = "logseq-keyword",
4120
}
4221

43-
function removeTimestamps(content: string): string {
44-
return content
45-
.replace(/doing:: (?:\d{13})/gms, "")
46-
.replace(/done:: (?:\d{13})/gms, "")
47-
.replace(/todo:: (?:\d{13})/gms, "")
48-
.replace(/doing:: (?:\d{13})/gms, "")
49-
.replace(/later:: (?:\d{13})/gms, "")
50-
.replace(/canceled:: (?:\d{13})/gms, "")
51-
.replace(
52-
/id:: (?:[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12})/gims,
53-
""
54-
)
55-
.replace(/collapsed:: (?:true|false)/gms, "")
56-
.replace("<br>", "");
57-
}
22+
const VERSION = "0.0.4";
5823

59-
const blockTest = new RegExp(/\#\+BEGIN_(WARNING|IMPORTANT|QUOTE|CAUTION)/gms);
24+
class LogSeqRegExes {
25+
static parseTaskType(content: string): TaskType {
26+
if (content.startsWith("DONE ")) {
27+
return TaskType.DONE;
28+
} else if (content.startsWith("TODO ")) {
29+
return TaskType.TODO;
30+
} else if (content.startsWith("DOING ")) {
31+
return TaskType.DOING;
32+
} else if (content.startsWith("LATER ")) {
33+
return TaskType.LATER;
34+
} else if (content.startsWith("CANCELED ")) {
35+
return TaskType.CANCELED;
36+
} else {
37+
return TaskType.UNKNOWN;
38+
}
39+
}
40+
41+
static HEADING_REGEX = {
42+
h1: /(?:\s+)?- # (?:.*)$/gms,
43+
h2: /(?:\s+)?- ## (?:.*)$/gms,
44+
h3: /(?:\s+)?- ### (?:.*)$/gms,
45+
h4: /(?:\s+)?- #### (?:.*)$/gms,
46+
h5: /(?:\s+)?- ##### (?:.*)$/gms,
47+
};
6048

61-
function isBlock(content: string): boolean {
62-
return blockTest.test(content);
49+
static BEGIN_BLOCK_REGEX = new RegExp(
50+
/\#\+BEGIN_(WARNING|IMPORTANT|QUOTE|CAUTION)/gms
51+
);
52+
static END_BLOCK_REGEX = new RegExp(
53+
/\#\+END_(WARNING|IMPORTANT|QUOTE|CAUTION)/gms
54+
);
55+
56+
static isBlock(content: string): boolean {
57+
return LogSeqRegExes.BEGIN_BLOCK_REGEX.test(content);
58+
}
6359
}
6460

65-
function cmHeadingOverlay(cm: CodeMirror.Editor) {
66-
cm.addOverlay({
61+
class CodeMirrorOverlays {
62+
static headingsOverlay = {
6763
token: (stream: any) => {
68-
if (stream.match(HEADING_REGEX["h1"])) {
64+
if (stream.match(LogSeqRegExes.HEADING_REGEX["h1"])) {
6965
return "header-1";
70-
} else if (stream.match(HEADING_REGEX["h2"])) {
66+
} else if (stream.match(LogSeqRegExes.HEADING_REGEX["h2"])) {
7167
return "header-2";
72-
} else if (stream.match(HEADING_REGEX["h3"])) {
68+
} else if (stream.match(LogSeqRegExes.HEADING_REGEX["h3"])) {
7369
return "header-3";
74-
} else if (stream.match(HEADING_REGEX["h4"])) {
70+
} else if (stream.match(LogSeqRegExes.HEADING_REGEX["h4"])) {
7571
return "header-4";
76-
} else if (stream.match(HEADING_REGEX["h5"])) {
72+
} else if (stream.match(LogSeqRegExes.HEADING_REGEX["h5"])) {
7773
return "header-5";
7874
} else {
7975
stream.next();
8076
}
8177
},
82-
});
78+
};
79+
static cmAddHeadingOverlay(cm: CodeMirror.Editor) {
80+
cm.addOverlay(CodeMirrorOverlays.headingsOverlay);
81+
}
82+
83+
static cmRemoveHeadingOverlay(cm: CodeMirror.Editor) {
84+
cm.removeOverlay(CodeMirrorOverlays.headingsOverlay);
85+
}
86+
}
87+
88+
function createKeywordElement(keyword: string): HTMLElement {
89+
const element = document.createElement("span");
90+
element.classList.add(TaskCSSClass.KEYWORD);
91+
element.textContent = keyword;
92+
return element;
93+
}
94+
95+
function createCheckboxElement(checked: boolean = false): HTMLElement {
96+
const element = document.createElement("input");
97+
element.type = "checkbox";
98+
element.checked = checked;
99+
return element;
83100
}
84101

85102
export default class LogSeqPlugin extends Plugin {
103+
static removeProperties(content: string): string {
104+
return content
105+
.replace(/doing:: (?:\d{13})/, "")
106+
.replace(/done:: (?:\d{13})/, "")
107+
.replace(/todo:: (?:\d{13})/, "")
108+
.replace(/doing:: (?:\d{13})/, "")
109+
.replace(/later:: (?:\d{13})/, "")
110+
.replace(/canceled:: (?:\d{13})/, "")
111+
.replace(
112+
/id:: (?:[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12})/i,
113+
""
114+
)
115+
.replace(/collapsed:: (?:true|false)/gms, "");
116+
}
117+
118+
static processChildren(el: Element, keyword: string) {
119+
el.childNodes.forEach((child) => {
120+
if (child.nodeType == Node.TEXT_NODE) {
121+
if (child.nodeValue.startsWith(keyword)) {
122+
child.nodeValue = child.nodeValue.replace(keyword, "");
123+
}
124+
child.nodeValue = LogSeqPlugin.removeProperties(child.nodeValue);
125+
}
126+
});
127+
}
128+
129+
static styleNode(el: Element, classname: TaskCSSClass) {
130+
el.querySelectorAll("li[data-line]").forEach((child) => {
131+
// Do not "complete" the child tasks, since this is LogSeq's behaviour
132+
child.classList.add(TaskCSSClass.INCOMPLETE);
133+
});
134+
el.classList.add(classname);
135+
}
136+
86137
static postprocessor: MarkdownPostProcessor = (
87138
el: HTMLElement,
88139
ctx: MarkdownPostProcessorContext
89140
) => {
90141
const entries = el.querySelectorAll("li[data-line]");
91142

92143
entries.forEach((entry) => {
93-
const taskType = parseTaskType(entry.textContent);
94-
95144
// Check if the entry is a org-mode block
96-
if (isBlock(entry.innerHTML)) {
145+
if (LogSeqRegExes.isBlock(entry.innerHTML)) {
97146
let replacedBlock = entry.innerHTML.replace(
98-
/\#\+BEGIN_(WARNING|IMPORTANT|QUOTE|CAUTION)/,
147+
LogSeqRegExes.BEGIN_BLOCK_REGEX,
99148
"<blockquote> &#9759;"
100149
);
101150
replacedBlock = replacedBlock.replace(
102-
/\#\+END_(WARNING|IMPORTANT|QUOTE|CAUTION)/,
151+
LogSeqRegExes.END_BLOCK_REGEX,
103152
"</blockquote>"
104153
);
105154
entry.innerHTML = replacedBlock;
106155
}
156+
const taskType = LogSeqRegExes.parseTaskType(entry.textContent);
107157

108158
if (taskType == TaskType.DONE) {
109-
const replacedHTML = removeTimestamps(
110-
entry.innerHTML.replace("DONE", "")
111-
);
112-
entry.innerHTML = `<span class="logseq-done-task"><input type="checkbox" checked> ${replacedHTML}</span>`;
159+
LogSeqPlugin.processChildren(entry, TaskType.DONE);
160+
161+
entry.insertAdjacentElement("afterbegin", createCheckboxElement(true));
162+
LogSeqPlugin.styleNode(entry, TaskCSSClass.COMPLETE);
113163
} else if (taskType == TaskType.TODO) {
114-
const replacedHTML = removeTimestamps(
115-
entry.innerHTML.replace("TODO", "")
164+
LogSeqPlugin.processChildren(entry, TaskType.TODO);
165+
166+
entry.insertAdjacentElement(
167+
"afterbegin",
168+
createKeywordElement(TaskType.TODO)
116169
);
117-
entry.innerHTML = `<input type="checkbox"> <span class="logseq-status-task">TODO</span> ${replacedHTML}`;
170+
171+
entry.insertAdjacentElement("afterbegin", createCheckboxElement());
172+
LogSeqPlugin.styleNode(entry, TaskCSSClass.INCOMPLETE);
118173
} else if (taskType == TaskType.DOING) {
119-
const replacedHTML = removeTimestamps(
120-
entry.innerHTML.replace("DOING", "")
174+
LogSeqPlugin.processChildren(entry, TaskType.DOING);
175+
176+
entry.insertAdjacentElement(
177+
"afterbegin",
178+
createKeywordElement(TaskType.DOING)
121179
);
122-
entry.innerHTML = `<input type="checkbox"> <span class="logseq-status-task">DOING</span> ${replacedHTML}`;
180+
181+
entry.insertAdjacentElement("afterbegin", createCheckboxElement());
123182
} else if (taskType == TaskType.LATER) {
124-
const replacedHTML = removeTimestamps(
125-
entry.innerHTML.replace("LATER", "")
183+
LogSeqPlugin.processChildren(entry, TaskType.LATER);
184+
185+
entry.insertAdjacentElement(
186+
"afterbegin",
187+
createKeywordElement(TaskType.LATER)
126188
);
127-
entry.innerHTML = `<input type="checkbox"> <span class="logseq-status-task">LATER</span> ${replacedHTML}`;
189+
190+
entry.insertAdjacentElement("afterbegin", createCheckboxElement());
191+
LogSeqPlugin.styleNode(entry, TaskCSSClass.INCOMPLETE);
128192
} else if (taskType == TaskType.CANCELED) {
129-
const replacedHTML = removeTimestamps(
130-
entry.innerHTML.replace("CANCELED", "")
131-
);
132-
entry.innerHTML = `<span class="logseq-done-task">${replacedHTML}</span>`;
193+
LogSeqPlugin.processChildren(entry, TaskType.CANCELED);
194+
LogSeqPlugin.styleNode(entry, TaskCSSClass.COMPLETE);
133195
}
134196
});
135197
};
136198

137199
onload() {
138-
console.log(`Loading LogSeq plugin ${VERSION}`);
139-
MarkdownPreviewRenderer.registerPostProcessor(LogSeqPlugin.postprocessor);
200+
console.log(`Loading logseq-compat plugin ${VERSION}`);
201+
this.registerMarkdownPostProcessor(LogSeqPlugin.postprocessor);
140202
// Style headings in source editing
141-
this.registerCodeMirror(cmHeadingOverlay);
203+
this.registerCodeMirror(CodeMirrorOverlays.cmAddHeadingOverlay);
142204
}
143205

144206
onunload() {
145-
console.log(`unloading LogSeq plugin ${VERSION}`);
146-
MarkdownPreviewRenderer.unregisterPostProcessor(LogSeqPlugin.postprocessor);
207+
console.log(`unloading logseq-compat plugin ${VERSION}`);
208+
this.registerCodeMirror(CodeMirrorOverlays.cmRemoveHeadingOverlay);
147209
}
148210
}

manifest.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
{
2-
"id": "logseq-format",
3-
"name": "LogSeq markdown formatting",
4-
"version": "0.0.3",
5-
"minAppVersion": "0.9.15",
6-
"description": "Render LogSeq-specific markdown",
7-
"author": "Rui Vieira",
8-
"authorUrl": "https://github.com/ruivieira/obsidian-plugin-logseq",
9-
"isDesktopOnly": false,
10-
"css": "style.css"
2+
"id": "logseq-compat",
3+
"name": "LogSeq markdown compatibility plugin",
4+
"version": "0.0.4",
5+
"minAppVersion": "0.9.15",
6+
"description": "Render LogSeq-specific markdown",
7+
"author": "Rui Vieira",
8+
"authorUrl": "https://github.com/ruivieira/obsidian-plugin-logseq",
9+
"isDesktopOnly": false,
10+
"css": "style.css"
1111
}

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "obsidian-plugin-logseq",
3-
"version": "0.0.3",
2+
"name": "logseq-compat",
3+
"version": "0.0.4",
44
"description": "Render LogSeq-specific markdown.",
55
"main": "main.js",
66
"scripts": {

styles.css

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,27 @@
1-
.logseq-done-task {
2-
text-decoration: line-through;
1+
li.logseq-complete-task:not(ul) {
2+
text-decoration: line-through !important;
33
opacity: 0.5;
4+
display: inline-block;
45
}
56

6-
.logseq-status-task {
7+
li.logseq-incomplete-task {
8+
text-decoration: none !important;
9+
display: inline-block;
10+
opacity: 1.0;
11+
}
12+
13+
14+
span.logseq-keyword {
715
font-weight: bold;
816
font-size: 0.9rem;
17+
margin-left: 0.5rem;
18+
margin-right: 0.5rem;
919
color: var(--text-a);
1020
}
1121

12-
a[href="#A"],a[href="#B"],a[href="#C"],a[href="#D"] {
22+
a[href="#A"],
23+
a[href="#B"],
24+
a[href="#C"],
25+
a[href="#D"] {
1326
color: var(--faded-blue) !important;
1427
}

versions.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
{
22
"0.0.1": "0.9.15",
33
"0.0.2": "0.9.15",
4-
"0.0.3": "0.9.15"
4+
"0.0.3": "0.9.15",
5+
"0.0.4": "0.9.15"
56
}

0 commit comments

Comments
 (0)