true init commit

This commit is contained in:
2023-08-23 15:40:08 +03:00
parent f8f4ba76ca
commit 2b7ca2afa5
10 changed files with 4238 additions and 174 deletions

View File

@@ -15,7 +15,7 @@ esbuild.build({
banner: {
js: banner,
},
entryPoints: ['main.ts'],
entryPoints: ['src/main.ts'],
bundle: true,
external: [
'obsidian',

137
main.ts
View File

@@ -1,137 +0,0 @@
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
// Remember to rename these classes and interfaces!
interface MyPluginSettings {
mySetting: string;
}
const DEFAULT_SETTINGS: MyPluginSettings = {
mySetting: 'default'
}
export default class MyPlugin extends Plugin {
settings: MyPluginSettings;
async onload() {
await this.loadSettings();
// This creates an icon in the left ribbon.
const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => {
// Called when the user clicks the icon.
new Notice('This is a notice!');
});
// Perform additional things with the ribbon
ribbonIconEl.addClass('my-plugin-ribbon-class');
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
const statusBarItemEl = this.addStatusBarItem();
statusBarItemEl.setText('Status Bar Text');
// This adds a simple command that can be triggered anywhere
this.addCommand({
id: 'open-sample-modal-simple',
name: 'Open sample modal (simple)',
callback: () => {
new SampleModal(this.app).open();
}
});
// This adds an editor command that can perform some operation on the current editor instance
this.addCommand({
id: 'sample-editor-command',
name: 'Sample editor command',
editorCallback: (editor: Editor, view: MarkdownView) => {
console.log(editor.getSelection());
editor.replaceSelection('Sample Editor Command');
}
});
// This adds a complex command that can check whether the current state of the app allows execution of the command
this.addCommand({
id: 'open-sample-modal-complex',
name: 'Open sample modal (complex)',
checkCallback: (checking: boolean) => {
// Conditions to check
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
if (markdownView) {
// If checking is true, we're simply "checking" if the command can be run.
// If checking is false, then we want to actually perform the operation.
if (!checking) {
new SampleModal(this.app).open();
}
// This command will only show up in Command Palette when the check function returns true
return true;
}
}
});
// This adds a settings tab so the user can configure various aspects of the plugin
this.addSettingTab(new SampleSettingTab(this.app, this));
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
// Using this function will automatically remove the event listener when this plugin is disabled.
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
console.log('click', evt);
});
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
}
onunload() {
}
async loadSettings() {
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
}
class SampleModal extends Modal {
constructor(app: App) {
super(app);
}
onOpen() {
const {contentEl} = this;
contentEl.setText('Woah!');
}
onClose() {
const {contentEl} = this;
contentEl.empty();
}
}
class SampleSettingTab extends PluginSettingTab {
plugin: MyPlugin;
constructor(app: App, plugin: MyPlugin) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const {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(this.plugin.settings.mySetting)
.onChange(async (value) => {
console.log('Secret: ' + value);
this.plugin.settings.mySetting = value;
await this.plugin.saveSettings();
}));
}
}

View File

@@ -1,10 +1,10 @@
{
"id": "obsidian-sample-plugin",
"name": "Sample Plugin",
"version": "1.0.1",
"minAppVersion": "0.12.0",
"description": "This is a sample plugin for Obsidian. This plugin demonstrates some of the capabilities of the Obsidian API.",
"author": "Obsidian",
"authorUrl": "https://obsidian.md",
"id": "obsidian-ldap-contact",
"name": "LDAP contacts lookup",
"version": "0.0.1",
"minAppVersion": "0.15.0",
"description": "A simple plugin for Obsidian that allows you to save contact information from LDAP to your vault.",
"author": "Maksim Syomochkin",
"authorUrl": "https://mak-sim.ru",
"isDesktopOnly": false
}

3820
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,8 @@
{
"name": "obsidian-sample-plugin",
"version": "1.0.1",
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
"main": "main.js",
"version": "0.0.1",
"description": "LDAP contacts lookup",
"main": "src/main.ts",
"scripts": {
"dev": "node esbuild.config.mjs",
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
@@ -12,7 +12,9 @@
"author": "",
"license": "MIT",
"devDependencies": {
"@types/ldapjs": "^2.2.5",
"@types/node": "^16.11.6",
"@types/nunjucks": "^3.2.3",
"@typescript-eslint/eslint-plugin": "5.29.0",
"@typescript-eslint/parser": "5.29.0",
"builtin-modules": "3.3.0",
@@ -20,5 +22,9 @@
"obsidian": "latest",
"tslib": "2.4.0",
"typescript": "4.7.4"
},
"dependencies": {
"ldapjs": "3.0.4",
"nunjucks": "^3.2.4"
}
}

109
src/main.ts Normal file
View File

@@ -0,0 +1,109 @@
import { createClient, SearchOptions, Error as LdapError, SearchCallbackResponse } from "ldapjs";
import {
LDAPSearchSettingTab,
DEFAULT_SETTINGS,
LDAPSearchSettings,
} from "./settings";
import { Notice, Plugin } from "obsidian";
import { LDAPModal } from "./modal";
import { renderString } from "nunjucks";
export default class LDAPSearch extends Plugin {
settings: LDAPSearchSettings;
async onload() {
await this.loadSettings();
console.log("LDAP search plugin started");
this.addCommand({
id: "ldap-contact-lookup",
name: "LDAP contact lookup",
callback: () => {
this.ldapsearch();
},
});
this.addSettingTab(new LDAPSearchSettingTab(this.app, this));
}
async ldapsearch() {
const client = createClient({
url: [`ldap://${this.settings.server}:${this.settings.port}`],
});
client.bind(this.settings.login, this.settings.password, (err) => {
if (err) {
new Notice(`LDAP Bind Error: ${err.message}`);
console.error("Bind ERROR", err);
}
});
new LDAPModal(this.app, (username) => {
const fields = this.settings.fields.split(",");
if (this.settings.photoFlag && !fields.contains(this.settings.photoField)){
fields.push(this.settings.photoField);
}
const opts: SearchOptions = {
filter: `(sAMAccountname=${username})`, //TODO: Move to settings
scope: "sub",
attributes: fields,
};
client.search(
this.settings.baseDN,
opts,
(err: LdapError, res: SearchCallbackResponse) => {
res.on("searchEntry", async (entry) => {
console.log("Entry finded");
const myMap: Map<string, string> = new Map<
string,
string
>();
entry.attributes.forEach(async (element) => {
if (element.type !== this.settings.photoField) {
myMap.set(element.type, element.vals[0]);
}
});
if (this.settings.photoFlag) {
entry.attributes.forEach(async (element) => {
if (element.type === this.settings.photoField) {
await app.vault.adapter.writeBinary(`${this.settings.photoPath}/${renderString(this.settings.photoNameTemplate, Object.fromEntries(myMap))}`,
element.buffers[0]
);
}
});
}
console.log(`${this.settings.path}/${renderString(this.settings.fileNameTempalte, Object.fromEntries(myMap))}`);
await app.vault.adapter.write(`${this.settings.path}/${renderString(this.settings.fileNameTempalte, Object.fromEntries(myMap))}`,
renderString(
this.settings.template,
Object.fromEntries(myMap)
))
});
res.on("error", (err) => {
new Notice(`LDAP Error: ${err.message}`);
console.error("error: " + err);
});
}
);
console.log("LDAP finish");
}).open();
}
onunload() {
console.log("LDAP Exit");
}
async loadSettings() {
this.settings = Object.assign(
{},
DEFAULT_SETTINGS,
await this.loadData()
);
}
async saveSettings() {
await this.saveData(this.settings);
}
}

35
src/modal.ts Normal file
View File

@@ -0,0 +1,35 @@
import { App, Modal, Setting } from "obsidian";
export class LDAPModal extends Modal {
username: string;
onSubmit: (username: string) => void;
constructor(app: App, onSubmit: (username: string) => void) {
super(app);
this.onSubmit = onSubmit;
}
onOpen(): void {
const { contentEl } = this;
contentEl.createEl("h1", { text: "Enter search term" });
new Setting(contentEl).setName("Search term").addText((text) =>
text.onChange((value) => {
this.username = value;
})
);
new Setting(contentEl)
.addButton((btn) =>
btn
.setButtonText("Submit")
.setCta()
.onClick(() => {
this.close();
this.onSubmit(this.username);
}));
}
onClose(): void {
const { contentEl } = this;
contentEl.empty();
}
}

239
src/settings.ts Normal file
View File

@@ -0,0 +1,239 @@
import { App, PluginSettingTab, Setting } from "obsidian";
import LDAPSearch from "./main";
export interface LDAPSearchSettings {
server: string;
port: string;
login: string;
password: string;
baseDN: string;
fileNameTempalte: string;
template: string;
fields: string;
path: string;
photoPath: string;
photoFlag: boolean;
photoNameTemplate: string;
photoField: string;
}
export const DEFAULT_SETTINGS: LDAPSearchSettings = {
server: "",
port: "389",
login: "",
password: "",
baseDN: "",
fileNameTempalte: "",
template: "",
fields: "",
path: "",
photoFlag: false,
photoPath: "",
photoNameTemplate: "",
photoField: "thumbnailPhoto"
};
export class LDAPSearchSettingTab extends PluginSettingTab {
plugin: LDAPSearch;
constructor(app: App, plugin: LDAPSearch) {
super(app, plugin);
this.plugin = plugin;
}
display(): void {
const { containerEl } = this;
containerEl.empty();
containerEl.createEl("h2", { text: "LDAP Search settings." });
new Setting(containerEl).setName("Login").
setDesc("Login or BindDN for LDAP server auth").
addText((text) =>
text
.setValue(this.plugin.settings.login)
.onChange(async (value) => {
this.plugin.settings.login = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl).setName("Password").
setDesc("Password for LDAP server auth").
addText((text) =>
text
.setValue(this.plugin.settings.password)
.onChange(async (value) => {
this.plugin.settings.password = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl).setName("Host").
setDesc("LDAP server host").
addText((text) =>
text
.setValue(this.plugin.settings.server)
.onChange(async (value) => {
this.plugin.settings.server = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl).setName("Port").
setDesc("LDAP server port").
addText((text) =>
text
.setValue(this.plugin.settings.port)
.setPlaceholder("389")
.onChange(async (value) => {
this.plugin.settings.port = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl).setName("BaseDN").
setDesc(createFragment((fragment) => {
fragment.append(
"Base object for search. See details ",
fragment.createEl("a", {
text: "here",
href: "https://en.wikipedia.org/wiki/Lightweight_Directory_Access_Protocol#Search_and_compare",
})
);
})).
addText((text) =>
text
.setValue(this.plugin.settings.baseDN)
.onChange(async (value) => {
this.plugin.settings.baseDN = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl).setName("Fields").
setDesc("Comma-separated list of fields to be retrieved from LDAP").
addText((text) =>
text
.setValue(this.plugin.settings.fields)
.onChange(async (value) => {
this.plugin.settings.fields = value;
await this.plugin.saveSettings();
})
);
new Setting(containerEl).setName("Target folder").
setDesc("Folder where the contact page will be saved").
addText((text) => {
text
.setValue(this.plugin.settings.path)
.setPlaceholder("/people")
.onChange(async (value) => {
this.plugin.settings.path = value;
await this.plugin.saveSettings();
})
});
new Setting(containerEl).setName("File name template").
setDesc(createFragment((fragment) => {
fragment.append(
"Template for filling with fields received from LDAP. You can use ",
fragment.createEl("a", {
text: "Nunjucks",
href: "https://mozilla.github.io/nunjucks/templating.html",
}),
" library specifications"
);
})).
addText((text)=>{
text
.setValue(this.plugin.settings.fileNameTempalte)
.setPlaceholder("{{ sn }} {{ givenName }}.md")
.onChange(async (value) => {
this.plugin.settings.fileNameTempalte = value;
await this.plugin.saveSettings();
})
})
new Setting(containerEl).setName("Page template").
setDesc(createFragment((fragment) => {
fragment.append(
"Template for filling with fields received from LDAP. You can use ",
fragment.createEl("a", {
text: "Nunjucks",
href: "https://mozilla.github.io/nunjucks/templating.html",
}),
" library specifications"
);
})).
addTextArea((text) => {
text
.setValue(this.plugin.settings.template)
.onChange(async (value) => {
this.plugin.settings.template = value;
await this.plugin.saveSettings();
})
text.inputEl.setAttr("rows", 25);
text.inputEl.setAttr("cols", 50);
}
);
containerEl.createEl("h5", {
text: "Advanced Settings",
});
new Setting(containerEl)
.setName("Store photo")
.setDesc("Enable or disable saving photo from ldap")
.addToggle((toggle) => {
toggle.setValue(this.plugin.settings.photoFlag)
.onChange(async (value) => {
this.plugin.settings.photoFlag = value;
await this.plugin.saveSettings();
})
})
new Setting(containerEl)
.setName("Photo LDAP fields")
.setDesc("LDAP fields contains photo")
.addText((text)=>{
text.setValue(this.plugin.settings.photoField)
.onChange(async (value) => {
this.plugin.settings.photoField = value;
await this.plugin.saveSettings();
})
})
new Setting(containerEl)
.setName("Target folder")
.setDesc("Folder where the contact photo will be saved")
.addText((text) => {
text.setValue(this.plugin.settings.photoPath)
.onChange(async (value) => {
this.plugin.settings.photoPath = value;
await this.plugin.saveSettings();
})
});
new Setting(containerEl)
.setName("Photo file name template")
.setDesc(createFragment((fragment)=>{
fragment.append(
"Template for photo file name. You can use the same fields as in the page template. The ",
fragment.createEl("a", {
text: "Nunjucks",
href: "https://mozilla.github.io/nunjucks/templating.html",
}),
" library is used"
)
}))
.addText((text)=>{
text.setValue(this.plugin.settings.photoNameTemplate)
.onChange(async (value) => {
this.plugin.settings.photoNameTemplate = value;
await this.plugin.saveSettings();
})
})
}
}

View File

@@ -1,4 +0,0 @@
/* Sets all the text color to red! */
body {
color: red;
}

View File

@@ -1,24 +1,20 @@
{
"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": ".src/",
"inlineSourceMap": true,
"inlineSources": true,
"module": "ESNext",
"target": "ES6",
"allowJs": true,
"noImplicitAny": true,
"moduleResolution": "node",
"importHelpers": true,
"isolatedModules": true,
"strictNullChecks": true,
"paths": {
"src": ["src/*", "tests/*"]
},
"lib": ["DOM", "ES5", "ES6", "ES7"]
},
"include": ["**/*.ts"]
}