Compare commits

..

No commits in common. "main" and "1.0.0" have entirely different histories.
main ... 1.0.0

19 changed files with 176 additions and 2805 deletions

View File

@ -1,10 +0,0 @@
# top-most EditorConfig file
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 4
tab_width = 4

View File

@ -1,3 +0,0 @@
node_modules/
main.js

View File

@ -1,23 +0,0 @@
{
"root": true,
"parser": "@typescript-eslint/parser",
"env": { "node": true },
"plugins": [
"@typescript-eslint"
],
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parserOptions": {
"sourceType": "module"
},
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
"@typescript-eslint/ban-ts-comment": "off",
"no-prototype-builtins": "off",
"@typescript-eslint/no-empty-function": "off"
}
}

View File

@ -1,84 +0,0 @@
name: Build obsidian plugin
on:
push:
# Sequence of patterns matched against refs/tags
tags:
- "*" # Push events to matching any tag format, i.e. 1.0, 20.15.10
env:
PLUGIN_NAME: swarnam # Change this to the name of your plugin-id folder
jobs:
build:
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Use Node.js
uses: actions/setup-node@v1
with:
node-version: "20.x"
- name: Build
id: build
run: |
npm i
npm run build
mkdir ${{ env.PLUGIN_NAME }}
cp main.js manifest.json styles.css ${{ env.PLUGIN_NAME }}
zip -r ${{ env.PLUGIN_NAME }}.zip ${{ env.PLUGIN_NAME }}
ls
echo "::set-output name=tag_name::$(git tag --sort version:refname | tail -n 1)"
- name: Create Release
id: create_release
uses: actions/create-release@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VERSION: ${{ github.ref }}
with:
tag_name: ${{ github.ref }}
release_name: ${{ github.ref }}
draft: false
prerelease: false
- name: Upload zip file
id: upload-zip
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./${{ env.PLUGIN_NAME }}.zip
asset_name: ${{ env.PLUGIN_NAME }}-${{ steps.build.outputs.tag_name }}.zip
asset_content_type: application/zip
- name: Upload main.js
id: upload-main
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./main.js
asset_name: main.js
asset_content_type: text/javascript
- name: Upload manifest.json
id: upload-manifest
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./manifest.json
asset_name: manifest.json
asset_content_type: application/json
- name: Upload styles.css
id: upload-css
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./styles.css
asset_name: styles.css
asset_content_type: text/css

17
.gitignore vendored
View File

@ -1,22 +1,11 @@
# vscode
.vscode
# Intellij
*.iml
.idea
# npm
node_modules
package-lock.json
# Don't include the compiled main.js file in the repo.
# They should be uploaded to GitHub releases instead.
# build
main.js
# Exclude sourcemaps
*.map
# obsidian
data.json
# Exclude macOS Finder (System Explorer) View States
.DS_Store
*.js.map

1
.npmrc
View File

@ -1 +0,0 @@
tag-version-prefix=""

Binary file not shown.

Before

Width:  |  Height:  |  Size: 403 KiB

View File

@ -1,20 +0,0 @@
Copyright (c) 2024 Sangeeth Sudheer
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -1,76 +1,22 @@
<div align="center">
<img src="./3dicons-co-icon.png" height="60px">
# Swarnam
A no-frills web playground plugin for Obsidian. Inspired by [MDN playgrounds](https://developer.mozilla.org/en-US/docs/Web/CSS/grid).
<sup><i>Swarnam (സ്വർണം)</i> means <i>gold</i> in Malayalam.<br/></sup>
<sup>(icon credit: [3dicons](https://3dicons.co))</sup>
</div>
## Demo
https://github.com/runofthemillgeek/swarnam-obsidian/assets/2759499/333424b3-2f82-4c1e-9e50-68c837227433
## Usage
Install the extension from Obsidian community plugins list (you need to enable this first from
Settings), enable the plugin and then restart Obsidian.
To create a new Swarnam block, you need to open a block code snippet and give it the `swarnam` tag
like so:
```swarnam
<h1>Hello, world</h1>
```
When you preview this, it'll show the source and render the HTML side-by-side. You can hover on the
top-right and click the `</>` icon to edit the snippet to make some changes.
You can also add CSS and JS by separating them with `---*---` like so:
```swarnam
<h1 id="h1">Hello world</h1>
---*---
h1 {
font-family: "Manjari";
color: red;
animation: rainbow 5s ease infinite forwards;
}
@keyframes rainbow {
0% { filter: hue-rotate(0); }
100% { filter: hue-rotate(360deg); }
}
---*---
let i = 0;
setInterval(() => {
h1.textContent = `${["Hello", "Hola", "നമസ്കാരം"][i++ % 3]} Obsidian!`;
}, 1500)
```
This'll render the three snippets on the left side and show the preview of a web page that has all
these 3 blocks injected.
## How does it work
We split the code block snippet into 3 parts and form an HTML document string by injecting the CSS
and JS pieces into `<style>` and `<script>` tags. Once we have the final HTML document, we convert
this into Base64 and create a data URI. Finally, an `<iframe>` element is created and the data URI
is given as the `src`. Data URIs of mime type `text/html` and base64 enoding can be rendered by most
browsers including the Chromium renderer Obsidian is built on top of. There are no additional build
steps involved and thus, this is not a full-blown replacement for something like Sandpack.
## Contributing
Spot any issue? Have a feature request or idea? Feel free to create a new issue in GitHub to
discuss. (Pretty please do this before you spend your precious time on a PR that might not make into
this repo).
## Obsidian Sample Plugin
This is a sample plugin for Obsidian (https://obsidian.md).
This project uses Typescript to provide type checking and documentation.
The repo contains the latest plugin API (obsidian.d.ts) in Typescript Definition format, which contains TSDoc comments describing what it does.
**Note:** The Obsidian API is still in early alpha and is subject to change at any time!
### How to use
- Clone this repo.
- `npm i` or `yarn` to install dependencies
- `npm run dev` to start compilation in watch mode.
### How to install the plugin
- Copy over `main.js`, `styles.css`, `manifest.json` to your vault `vault/.obsidian/plugins/plugin-id/`.
### API Documentation
See https://github.com/obsidianmd/obsidian-api

View File

@ -1,48 +0,0 @@
import esbuild from "esbuild";
import process from "process";
import builtins from "builtin-modules";
const banner =
`/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
`;
const prod = (process.argv[2] === "production");
const context = await esbuild.context({
banner: {
js: banner,
},
entryPoints: ["main.ts"],
bundle: true,
external: [
"obsidian",
"electron",
"@codemirror/autocomplete",
"@codemirror/collab",
"@codemirror/commands",
"@codemirror/language",
"@codemirror/lint",
"@codemirror/search",
"@codemirror/state",
"@codemirror/view",
"@lezer/common",
"@lezer/highlight",
"@lezer/lr",
...builtins],
format: "cjs",
target: "es2018",
logLevel: "info",
sourcemap: prod ? false : "inline",
treeShaking: true,
outfile: "main.js",
});
if (prod) {
await context.rebuild();
process.exit(0);
} else {
await context.watch();
}

218
main.ts
View File

@ -1,141 +1,77 @@
import { Plugin } from "obsidian";
const PREFIX = "swarnam";
function base64ToBytes(base64: string) {
const binString = atob(base64);
// @ts-expect-error
return Uint8Array.from(binString, (m) => m.codePointAt(0));
}
function bytesToBase64(bytes: Uint8Array) {
const binString = Array.from(bytes, (byte) =>
String.fromCodePoint(byte)
).join("");
return btoa(binString);
}
function asBase64(text: string) {
const b64 = bytesToBase64(new TextEncoder().encode(text));
return b64;
}
function getIframeDoc(htmlSource: string, cssSource: string, jsSource: string) {
const isDarkMode = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
return `
<style>
body { font-family: sans-serif; color: ${isDarkMode ? "#fff" : "#000"} }
</style>
<style>
${cssSource}
</style>
<div class="${PREFIX}-html-container">
${htmlSource}
</div>
<script>
${jsSource}
</script>
`;
}
function showError(msg: string, root: HTMLElement) {
root.classList.add("error");
root.createEl("p", { cls: "icon", text: "⚠️" });
root.createEl("p", { text: msg });
}
export default class SwarnamPlugin extends Plugin {
async onload() {
this.registerMarkdownCodeBlockProcessor(
"swarnam",
(source, el, ctx) => {
const root = el.createDiv({ cls: `${PREFIX}-root` });
let [
htmlSource = "",
cssSource = "",
jsSource = "",
// eslint-disable-next-line prefer-const
...others
] = source.split(/^\s*---\*---\s*$/m);
if (others.length > 0) {
showError(
"Swarnam only supports HTML, CSS and JS blocks but your snippet has more than 3 blocks.",
root
);
return;
}
htmlSource = htmlSource.trim();
cssSource = cssSource.trim();
jsSource = jsSource.trim();
if (!htmlSource) {
showError(
"A Swarnam block must at least contain HTML",
root
);
return;
}
const sourceRoot = root.createDiv({
cls: `${PREFIX}-source-root`,
});
const htmlContainer = sourceRoot.createDiv({
cls: `${PREFIX}-source-container`,
});
const htmlEl = htmlContainer.createEl("pre", {
cls: `${PREFIX}-source ${PREFIX}-html-source`,
});
htmlContainer.createDiv({
text: "HTML",
cls: `${PREFIX}-badge ${PREFIX}-html-badge`,
});
htmlEl.setText(htmlSource);
if (cssSource) {
const cssContainer = sourceRoot.createDiv({
cls: `${PREFIX}-source-container`,
});
const cssEl = cssContainer.createEl("pre", {
cls: `${PREFIX}-source ${PREFIX}-css-source`,
});
cssContainer.createDiv({
text: "CSS",
cls: `${PREFIX}-badge ${PREFIX}-css-badge`,
});
cssEl.setText(cssSource);
}
if (jsSource) {
const jsContainer = sourceRoot.createDiv({
cls: `${PREFIX}-source-container`,
});
const jsEl = jsContainer.createEl("pre", {
cls: `${PREFIX}-source ${PREFIX}-js-source`,
});
jsContainer.createDiv({
text: "JS",
cls: `${PREFIX}-badge ${PREFIX}-js-badge`,
});
jsEl.setText(jsSource);
}
const iframeEl = root.createEl("iframe", {
cls: `${PREFIX}-preview`,
});
iframeEl.src = `data:text/html;base64;charset=UTF-8,${asBase64(
getIframeDoc(htmlSource, cssSource, jsSource)
)}`;
}
);
}
}
import { App, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
export default class MyPlugin extends Plugin {
onInit() {
}
onload() {
console.log('loading plugin');
this.addRibbonIcon('dice', 'Sample Plugin', () => {
new Notice('This is a notice!');
});
this.addStatusBarItem().setText('Status Bar Text');
this.addCommand({
id: 'open-sample-modal',
name: 'Open Sample Modal',
// callback: () => {
// console.log('Simple Callback');
// },
checkCallback: (checking: boolean) => {
let leaf = this.app.workspace.activeLeaf;
if (leaf) {
if (!checking) {
new SampleModal(this.app).open();
}
return true;
}
return false;
}
});
this.addSettingTab(new SampleSettingTab(this.app, this));
}
onunload() {
console.log('unloading plugin');
}
}
class SampleModal extends Modal {
constructor(app: App) {
super(app);
}
onOpen() {
let {contentEl} = this;
contentEl.setText('Woah!');
}
onClose() {
let {contentEl} = this;
contentEl.empty();
}
}
class SampleSettingTab extends PluginSettingTab {
display(): void {
let {containerEl} = this;
containerEl.empty();
containerEl.createEl('h2', {text: 'Settings for my awesome plugin.'});
new Setting(containerEl)
.setName('Setting #1')
.setDesc('It\'s a secret')
.addText(text => text.setPlaceholder('Enter your secret')
.setValue('')
.onChange((value) => {
console.log('Secret: ' + value);
}));
}
}

View File

@ -1,11 +1,8 @@
{
"id": "swarnam",
"name": "Swarnam",
"version": "1.0.1",
"minAppVersion": "1.5.12",
"description": "A no-frills web playground plugin for Obsidian.",
"author": "runofthemillgeek",
"authorUrl": "https://dg.sangeeth.dev",
"fundingUrl": "https://www.buymeacoffee.com/runofthemillgeek",
"isDesktopOnly": true
}
{
"id": "obsidian-sample-plugin",
"name": "Sample Plugin",
"version": "1.0.0",
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
"author": "Licat",
"isDesktopOnly": false
}

2229
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,24 +1,23 @@
{
"name": "obsidian-sample-plugin",
"version": "1.0.1",
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
"main": "main.js",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
"version": "node version-bump.mjs && git add manifest.json versions.json"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@types/node": "^16.11.6",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
"esbuild": "0.17.3",
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4"
}
"name": "obsidian-sample-plugin",
"version": "0.9.7",
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
"main": "main.js",
"scripts": {
"dev": "rollup --config rollup.config.js -w",
"build": "rollup --config rollup.config.js"
},
"keywords": [],
"author": "",
"license": "MIT",
"devDependencies": {
"@rollup/plugin-commonjs": "^15.1.0",
"@rollup/plugin-node-resolve": "^9.0.0",
"@rollup/plugin-typescript": "^6.0.0",
"@types/node": "^14.14.2",
"obsidian": "https://github.com/obsidianmd/obsidian-api/tarball/master",
"rollup": "^2.32.1",
"tslib": "^2.0.3",
"typescript": "^4.0.3"
}
}

19
rollup.config.js Normal file
View File

@ -0,0 +1,19 @@
import typescript from '@rollup/plugin-typescript';
import {nodeResolve} from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';
export default {
input: 'main.ts',
output: {
dir: '.',
sourcemap: 'inline',
format: 'cjs',
exports: 'default'
},
external: ['obsidian'],
plugins: [
typescript(),
nodeResolve({browser: true}),
commonjs(),
]
};

View File

@ -1,81 +1,4 @@
.swarnam-root {
--swarnam-html-color: #e34c26;
--swarnam-css-color: #2965f1;
--swarnam-js-color: #f0db4f;
--swarnam-box-padding: var(--size-4-3) var(--size-4-4);
--swarnam-border-color: var(--divider-color);
--swarnam-border-radius: var(--code-radius);
--swarnam-source-font: var(--font-monospace);
--swarnam-source-font-size: var(--code-size);
display: flex;
align-items: stretch;
width: 100%;
}
.swarnam-root.error {
display: block;
padding: var(--swarnam-box-padding);
background-color: var(--code-background);
text-align: center;
.icon {
font-family: "Apple Emoji Color", sans-serif;
font-size: 1.5em;
}
}
.swarnam-source-root {
flex: 1 1 0;
overflow-x: scroll;
display: flex;
align-items: stretch;
flex-direction: column;
}
.swarnam-preview {
flex: 1 1 0;
border: 1px solid var(--swarnam-border-color);
border-left: 0;
border-top-right-radius: var(--swarnam-border-radius);
border-bottom-right-radius: var(--swarnam-border-radius);
padding: var(--swarnam-box-padding);
}
.swarnam-source-container {
position: relative;
flex: 1 1 0;
& + & {
border-top: 1px solid var(--swarnam-border-color);
border-top-left-radius: 0;
border-top-right-radius: 0;
}
}
.markdown-rendered .swarnam-source {
height: 100%;
margin: 0;
font-family: var(--swarnam-source-font);
font-size: var(--swarnam-source-font-size);
white-space: pre;
}
.swarnam-badge {
position: absolute;
top: calc(var(--size-4-3) / 1.5);
right: calc(var(--size-4-3) / 1.5);
font-size: 0.65em;
}
.swarnam-html-badge {
color: var(--swarnam-html-color);
}
.swarnam-css-badge {
color: var(--swarnam-css-color);
}
.swarnam-js-badge {
color: var(--swarnam-js-color);
}
/* Sets all the text color to red! */
body {
color: red;
}

View File

@ -1,24 +1,22 @@
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"lib": [
"DOM",
"ES5",
"ES6",
"ES7"
]
},
"include": [
"**/*.ts"
]
}
{
"compilerOptions": {
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "es5",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"lib": [
"dom",
"es5",
"scripthost",
"es2015"
]
},
"include": [
"**/*.ts"
]
}

View File

@ -1,14 +0,0 @@
import { readFileSync, writeFileSync } from "fs";
const targetVersion = process.env.npm_package_version;
// read minAppVersion from manifest.json and bump version to target version
let manifest = JSON.parse(readFileSync("manifest.json", "utf8"));
const { minAppVersion } = manifest;
manifest.version = targetVersion;
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
// update versions.json with target version and minAppVersion from manifest.json
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
versions[targetVersion] = minAppVersion;
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));

View File

@ -1,4 +0,0 @@
{
"1.0.0": "0.15.0",
"1.0.1": "0.15.0"
}