Skip to content

Commit 2faea41

Browse files
authored
first commit
1 parent 4d173ac commit 2faea41

File tree

4 files changed

+222
-2
lines changed

4 files changed

+222
-2
lines changed

README.md

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,61 @@
1-
# safeLearn-Obsidian-plugin
2-
A community plugin for Obsidian, that offers visual aids for the SafeLearn-specific tags.
1+
# SafeLearn Plugin for Obsidian
2+
3+
A community plugin for Obsidian that provides visual aids for [SafeLearn](https://github.com/UnterrainerInformatik/safeLearn)-specific Markdown tags. It enhances the editing experience by adding visual formatting for Reveal.js fragments, role-based permission blocks, and multi-column side-by-side layouts.
4+
5+
---
6+
7+
## ✨ Features
8+
9+
### 🔹 Fragment Support (`##fragment`)
10+
Marks content blocks that should appear incrementally in Reveal.js slides.
11+
12+
**Example:**
13+
```markdown
14+
This is visible immediately.
15+
16+
##fragment
17+
This will appear as a fragment.
18+
19+
##fragment
20+
- Step 1
21+
- Step 2
22+
```
23+
24+
### 🔹 Permission Blocks (`@@@ role`)
25+
Visually wraps blocks meant for specific roles (like teacher, 4bhif, etc.) to make them clearly distinguishable while editing.
26+
**Example:**
27+
```markdown
28+
@@@ teacher
29+
This block is for teachers only.
30+
@@@
31+
```
32+
33+
### 🔹 Side-by-Side Columns (##side-by-side-start, ##separator)
34+
Creates multi-column layouts for wide Reveal.js slides.
35+
36+
**Example:**
37+
```markdown
38+
##side-by-side-start
39+
Left side content.
40+
##separator
41+
Right side content.
42+
##side-by-side-end
43+
```
44+
45+
## 🛠️ Installation
46+
Clone or download this repository.
47+
48+
Copy the plugin folder into your Obsidian vault's .obsidian/plugins/ directory.
49+
50+
Enable the plugin in Obsidian's settings.
51+
52+
## 📦 Compatibility
53+
Obsidian v0.15.0 or later
54+
55+
No external dependencies
56+
57+
## 🔐 Disclaimer
58+
This plugin does not enforce permissions. It is purely visual. All security filtering is expected to be done on your SafeLearn rendering server (e.g., via Node.js and Keycloak).
59+
60+
## 📄 License
61+
[The Unlicense](https://github.com/UnterrainerInformatik/safeLearn-Obsidian-plugin#Unlicense-1-ov-file)

main.ts

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
/**
2+
* Obsidian Plugin: SafeLearn Plugin
3+
* Provides visual and structural enhancements for SafeLearn-specific tags,
4+
* including highlighting of '##fragment', permission-based visibility blocks,
5+
* and Reveal.js features like side-by-side columns.
6+
*/
7+
8+
import { Plugin } from "obsidian";
9+
10+
export default class SafeLearnPlugin extends Plugin {
11+
async onload() {
12+
this.addStyles();
13+
this.registerDomEvent(document, "DOMContentLoaded", () => {
14+
this.processAll();
15+
});
16+
this.registerEvent(
17+
this.app.workspace.on("layout-change", () => this.processAll())
18+
);
19+
}
20+
21+
onunload() {
22+
document.querySelectorAll(".fragment-highlight, .permission-block, .side-by-side").forEach(el => {
23+
el.classList.remove("fragment-highlight", "permission-block", "side-by-side");
24+
});
25+
}
26+
27+
addStyles() {
28+
const style = document.createElement("style");
29+
style.id = "safelearn-style";
30+
style.textContent = `
31+
.fragment-highlight {
32+
background-color: var(--fragment-bg);
33+
color: var(--fragment-fg);
34+
border: 2px solid var(--fragment-border);
35+
border-radius: 8px;
36+
padding: 2px 6px;
37+
margin: 1px;
38+
display: inline-block;
39+
font-weight: 500;
40+
}
41+
.fragment-highlight::before {
42+
content: "🔀 ";
43+
opacity: 0.6;
44+
}
45+
.permission-block {
46+
background-color: var(--permission-bg, #eef7ff);
47+
border-left: 4px solid var(--permission-border, #3399ff);
48+
padding: 4px 8px;
49+
margin: 8px 0;
50+
}
51+
.permission-block .perm-label,
52+
.permission-block .perm-end {
53+
font-family: monospace;
54+
font-size: 0.8em;
55+
background: #ddeeff;
56+
color: #225577;
57+
padding: 2px 4px;
58+
border-radius: 4px;
59+
display: inline-block;
60+
margin-bottom: 4px;
61+
}
62+
.side-by-side {
63+
display: flex;
64+
gap: 20px;
65+
margin: 1em 0;
66+
border: 1px dashed #bbb;
67+
padding: 8px;
68+
}
69+
.side-by-side > div {
70+
flex: 1;
71+
padding: 0 10px;
72+
border-left: 2px dashed #ccc;
73+
}
74+
`;
75+
document.head.appendChild(style);
76+
}
77+
78+
processAll() {
79+
const leaves = this.app.workspace.getLeavesOfType("markdown");
80+
for (const leaf of leaves) {
81+
const view = leaf.view;
82+
if (view.previewMode?.renderer) {
83+
const container = view.previewMode.containerEl;
84+
this.highlightFragments(container);
85+
this.markPermissionBlocks(container);
86+
this.convertSideBySide(container);
87+
}
88+
}
89+
}
90+
91+
highlightFragments(container: HTMLElement) {
92+
const walker = document.createTreeWalker(
93+
container,
94+
NodeFilter.SHOW_TEXT,
95+
{
96+
acceptNode: (node) => {
97+
return node.nodeValue?.match(/\s*##fragment\s*/)
98+
? NodeFilter.FILTER_ACCEPT
99+
: NodeFilter.FILTER_SKIP;
100+
},
101+
},
102+
false
103+
);
104+
105+
const toHighlight: Text[] = [];
106+
while (walker.nextNode()) {
107+
toHighlight.push(walker.currentNode as Text);
108+
}
109+
110+
for (const node of toHighlight) {
111+
const span = document.createElement("span");
112+
span.className = "fragment-highlight";
113+
span.textContent = node.nodeValue;
114+
node.parentNode?.replaceChild(span, node);
115+
}
116+
}
117+
118+
markPermissionBlocks(container: HTMLElement) {
119+
const blocks = container.innerHTML.match(/@@@[^@\n]+[\s\S]*?@@@/g);
120+
if (!blocks) return;
121+
for (const block of blocks) {
122+
const match = block.match(/^@@@([^\n]+)\n([\s\S]*?)\n@@@$/);
123+
if (!match) continue;
124+
const role = match[1].trim();
125+
const content = match[2].trim();
126+
const div = document.createElement("div");
127+
div.className = "permission-block";
128+
div.innerHTML = `<div class="perm-label">@@@ ${role}</div><div>${content}</div><div class="perm-end">@@@</div>`;
129+
container.innerHTML = container.innerHTML.replace(block, div.outerHTML);
130+
}
131+
}
132+
133+
convertSideBySide(container: HTMLElement) {
134+
const pattern = /##side-by-side-start([\s\S]*?)##side-by-side-end/g;
135+
container.innerHTML = container.innerHTML.replace(pattern, (match, content) => {
136+
const parts = content.split(/##separator/g);
137+
const columns = parts.map(col => `<div>${col.trim()}</div>`).join('');
138+
return `<div class="side-by-side">${columns}</div>`;
139+
});
140+
}
141+
}

manifest.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"id": "safelearn-plugin",
3+
"name": "SafeLearn Plugin",
4+
"version": "1.0.0",
5+
"minAppVersion": "0.15.0",
6+
"description": "A community plugin for Obsidian that offers visual aids for SafeLearn-specific tags such as ##fragment, permission blocks, and side-by-side layouts for Reveal.js.",
7+
"author": "Gerald Unterrainer",
8+
"authorUrl": "https://github.com/UnterrainerInformatik",
9+
"repo": "https://github.com/UnterrainerInformatik/safeLearn-Obsidian-plugin",
10+
"isDesktopOnly": false,
11+
"main": "main.js"
12+
}

styles.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
:root {
2+
--fragment-bg: #f3e6ff;
3+
--fragment-fg: #4b0082;
4+
--fragment-border: #4b0082;
5+
6+
--permission-bg: #eef7ff;
7+
--permission-border: #3399ff;
8+
}

0 commit comments

Comments
 (0)