├── .eslintignore ├── images ├── simple.png ├── comments.png ├── delimiter.png ├── dialogue.png ├── parameters.png └── parameters2.png ├── versions.json ├── src ├── types │ └── dialogueTitleMode.ts ├── main.ts ├── components │ ├── delimiter.ts │ ├── comment.ts │ └── message.ts ├── constants │ └── classes.ts ├── settings.ts └── dialogue.ts ├── .editorconfig ├── manifest.json ├── .gitignore ├── tsconfig.json ├── package.json ├── .eslintrc ├── esbuild.config.mjs ├── LICENSE ├── styles.css └── README.md /.eslintignore: -------------------------------------------------------------------------------- 1 | npm node_modules 2 | build -------------------------------------------------------------------------------- /images/simple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holubj/obsidian-dialogue-plugin/HEAD/images/simple.png -------------------------------------------------------------------------------- /versions.json: -------------------------------------------------------------------------------- 1 | { 2 | "1.0.2": "0.12.3", 3 | "1.0.1": "0.12.3", 4 | "1.0.0": "0.12.3" 5 | } 6 | -------------------------------------------------------------------------------- /images/comments.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holubj/obsidian-dialogue-plugin/HEAD/images/comments.png -------------------------------------------------------------------------------- /images/delimiter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holubj/obsidian-dialogue-plugin/HEAD/images/delimiter.png -------------------------------------------------------------------------------- /images/dialogue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holubj/obsidian-dialogue-plugin/HEAD/images/dialogue.png -------------------------------------------------------------------------------- /images/parameters.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holubj/obsidian-dialogue-plugin/HEAD/images/parameters.png -------------------------------------------------------------------------------- /images/parameters2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/holubj/obsidian-dialogue-plugin/HEAD/images/parameters2.png -------------------------------------------------------------------------------- /src/types/dialogueTitleMode.ts: -------------------------------------------------------------------------------- 1 | export enum DialogueTitleMode { 2 | Disabled = 'disabled', 3 | First = 'first', 4 | All = 'all', 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | insert_final_newline = true 7 | indent_style = tab 8 | indent_size = 4 9 | tab_width = 4 10 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "obsidian-dialogue-plugin", 3 | "name": "Dialogue", 4 | "version": "1.0.2", 5 | "minAppVersion": "0.12.0", 6 | "description": "Create dialogues in Markdown.", 7 | "author": "Jakub Holub", 8 | "authorUrl": "https://github.com/holubj", 9 | "isDesktopOnly": false 10 | } 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # vscode 2 | .vscode 3 | 4 | # Intellij 5 | *.iml 6 | .idea 7 | 8 | # npm 9 | node_modules 10 | package-lock.json 11 | 12 | # Don't include the compiled main.js file in the repo. 13 | # They should be uploaded to GitHub releases instead. 14 | main.js 15 | 16 | # Exclude sourcemaps 17 | *.map 18 | 19 | # obsidian 20 | data.json 21 | 22 | # macOS 23 | .DS_Store 24 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "inlineSourceMap": true, 5 | "inlineSources": true, 6 | "module": "ESNext", 7 | "target": "ES6", 8 | "allowJs": true, 9 | "noImplicitAny": true, 10 | "moduleResolution": "node", 11 | "importHelpers": true, 12 | "lib": [ 13 | "DOM", 14 | "ES5", 15 | "ES6", 16 | "ES7" 17 | ] 18 | }, 19 | "include": [ 20 | "**/*.ts" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "obsidian-dialogue-plugin", 3 | "version": "0.12.0", 4 | "description": "Create dialogues in Markdown.", 5 | "main": "main.js", 6 | "scripts": { 7 | "dev": "node esbuild.config.mjs", 8 | "build": "node esbuild.config.mjs production" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "MIT", 13 | "devDependencies": { 14 | "@types/node": "^16.11.6", 15 | "@typescript-eslint/eslint-plugin": "^5.2.0", 16 | "@typescript-eslint/parser": "^5.2.0", 17 | "builtin-modules": "^3.2.0", 18 | "esbuild": "0.13.12", 19 | "obsidian": "^0.12.17", 20 | "tslib": "2.3.1", 21 | "typescript": "4.4.4" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "plugins": [ 5 | "@typescript-eslint" 6 | ], 7 | "extends": [ 8 | "eslint:recommended", 9 | "plugin:@typescript-eslint/eslint-recommended", 10 | "plugin:@typescript-eslint/recommended" 11 | ], 12 | "parserOptions": { 13 | "sourceType": "module" 14 | }, 15 | "rules": { 16 | "no-unused-vars": "off", 17 | "@typescript-eslint/no-unused-vars": ["error", { "args": "none" }], 18 | "@typescript-eslint/ban-ts-comment": "off", 19 | "no-prototype-builtins": "off", 20 | "@typescript-eslint/no-empty-function": "off" 21 | } 22 | } -------------------------------------------------------------------------------- /esbuild.config.mjs: -------------------------------------------------------------------------------- 1 | import esbuild from "esbuild"; 2 | import process from "process"; 3 | import builtins from "builtin-modules"; 4 | 5 | const banner = `/* 6 | THIS IS A GENERATED/BUNDLED FILE BY ESBUILD 7 | if you want to view the source, please visit the github repository of this plugin 8 | */ 9 | `; 10 | 11 | const prod = process.argv[2] === "production"; 12 | 13 | esbuild 14 | .build({ 15 | banner: { 16 | js: banner, 17 | }, 18 | entryPoints: ["src/main.ts"], 19 | bundle: true, 20 | external: ["obsidian", "electron", ...builtins], 21 | format: "cjs", 22 | watch: !prod, 23 | target: "es2016", 24 | logLevel: "info", 25 | sourcemap: prod ? false : "inline", 26 | treeShaking: true, 27 | outfile: "main.js", 28 | }) 29 | .catch(() => process.exit(1)); 30 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { Plugin } from 'obsidian'; 2 | import { DialogueRenderer } from './dialogue'; 3 | import { DEFAULT_SETTINGS, DialoguePluginSettings, DialogueSettingTab } from './settings'; 4 | 5 | 6 | export default class DialoguePlugin extends Plugin { 7 | 8 | settings: DialoguePluginSettings; 9 | 10 | async onload() { 11 | await this.loadSettings(); 12 | 13 | this.registerMarkdownCodeBlockProcessor( 14 | `dialogue`, 15 | (src, el, ctx) => { 16 | new DialogueRenderer(src, el, this.settings); 17 | } 18 | ); 19 | 20 | this.addSettingTab(new DialogueSettingTab(this.app, this)); 21 | } 22 | 23 | async loadSettings() { 24 | this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData()); 25 | } 26 | 27 | async saveSettings() { 28 | await this.saveData(this.settings); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/components/delimiter.ts: -------------------------------------------------------------------------------- 1 | import { CLASSES } from '../constants/classes'; 2 | import { DialogueSettings } from '../dialogue'; 3 | 4 | export class Delimiter { 5 | 6 | dialogueSettings: DialogueSettings; 7 | 8 | constructor(dialogueSettings: DialogueSettings) { 9 | this.dialogueSettings = dialogueSettings; 10 | 11 | this.renderDelimiter(); 12 | } 13 | 14 | renderDelimiter() { 15 | const delimiterWrapperEl = this.dialogueSettings.parent.createDiv({ 16 | cls: `${CLASSES.BLOCK_WRAPPER} ${CLASSES.DELIMITER_WRAPPER}` 17 | }); 18 | 19 | const delimiterEl = delimiterWrapperEl.createDiv({ cls: CLASSES.DELIMITER }); 20 | 21 | delimiterEl.createEl('div', { cls: CLASSES.DELIMITER_DOT }); 22 | delimiterEl.createEl('div', { cls: CLASSES.DELIMITER_DOT }); 23 | delimiterEl.createEl('div', { cls: CLASSES.DELIMITER_DOT }); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/components/comment.ts: -------------------------------------------------------------------------------- 1 | import { CLASSES } from '../constants/classes'; 2 | import { DialogueSettings } from '../dialogue'; 3 | 4 | export class Comment { 5 | 6 | content: string; 7 | 8 | dialogueSettings: DialogueSettings; 9 | 10 | constructor(content: string, dialogueSettings: DialogueSettings) { 11 | this.content = content; 12 | this.dialogueSettings = dialogueSettings; 13 | 14 | this.renderComment(); 15 | } 16 | 17 | renderComment() { 18 | const commentEl = this.dialogueSettings.parent.createDiv({ 19 | cls: `${CLASSES.BLOCK_WRAPPER} ${CLASSES.COMMENT_WRAPPER}` 20 | }); 21 | 22 | return commentEl.createDiv({ 23 | cls: CLASSES.COMMENT, 24 | text: this.content, 25 | attr: { 26 | style: `max-width: ${this.dialogueSettings.commentMaxWidth};` 27 | } 28 | }); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/constants/classes.ts: -------------------------------------------------------------------------------- 1 | 2 | export abstract class CLASSES { 3 | static readonly DIALOGUE_WRAPPER = 'dialogue-plugin-wrapper'; 4 | static readonly BLOCK_WRAPPER = 'dialogue-plugin-block-wrapper'; 5 | static readonly MESSAGE_WRAPPER_LEFT = 'dialogue-plugin-message-wrapper-left'; 6 | static readonly MESSAGE_WRAPPER_RIGHT = 'dialogue-plugin-message-wrapper-right'; 7 | static readonly MESSAGE = 'dialogue-plugin-message'; 8 | static readonly MESSAGE_TITLE = 'dialogue-plugin-message-title'; 9 | static readonly MESSAGE_CONTENT = 'dialogue-plugin-message-content'; 10 | static readonly DELIMITER_WRAPPER = 'dialogue-plugin-delimiter-wrapper'; 11 | static readonly DELIMITER = 'dialogue-plugin-delimiter'; 12 | static readonly DELIMITER_DOT = 'dialogue-plugin-delimiter-dot'; 13 | static readonly COMMENT_WRAPPER = 'dialogue-plugin-comment-wrapper'; 14 | static readonly COMMENT = 'dialogue-plugin-comment'; 15 | } 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Jakub Holub 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /styles.css: -------------------------------------------------------------------------------- 1 | .dialogue-plugin-wrapper { 2 | margin-bottom: 20px; 3 | } 4 | 5 | .dialogue-plugin-block-wrapper { 6 | display: flex; 7 | margin: 10px 0; 8 | } 9 | 10 | .dialogue-plugin-message-wrapper-left { 11 | justify-content: start; 12 | } 13 | 14 | .dialogue-plugin-message-wrapper-right { 15 | justify-content: flex-end; 16 | } 17 | 18 | .dialogue-plugin-message { 19 | overflow: hidden; 20 | max-width: 60%; 21 | background-color: var(--background-secondary); 22 | } 23 | 24 | .dialogue-plugin-message-title { 25 | padding: 5px 10px; 26 | font-weight: bold; 27 | background-color: rgba(0, 0, 0, 0.3); 28 | } 29 | 30 | .dialogue-plugin-message-content { 31 | padding: 5px 10px; 32 | } 33 | 34 | .dialogue-plugin-delimiter-wrapper { 35 | justify-content: center; 36 | } 37 | 38 | .dialogue-plugin-delimiter { 39 | margin: 20px 0; 40 | } 41 | 42 | .dialogue-plugin-delimiter-dot { 43 | width: 10px; 44 | height: 10px; 45 | margin: 0 3px; 46 | display: inline-block; 47 | border-radius: 50%; 48 | background-color: var(--background-secondary); 49 | } 50 | 51 | .dialogue-plugin-comment-wrapper { 52 | justify-content: center; 53 | } 54 | 55 | .dialogue-plugin-comment { 56 | margin: 20px 0; 57 | text-align: center; 58 | } 59 | -------------------------------------------------------------------------------- /src/components/message.ts: -------------------------------------------------------------------------------- 1 | import { CLASSES } from '../constants/classes'; 2 | import { DialogueSettings, Participant } from '../dialogue'; 3 | import { DialogueTitleMode } from '../types/dialogueTitleMode'; 4 | 5 | export abstract class SIDES { 6 | static readonly LEFT = 'left'; 7 | static readonly RIGHT = 'right'; 8 | } 9 | 10 | export type MessageSide = typeof SIDES.LEFT | typeof SIDES.RIGHT; 11 | 12 | export class Message { 13 | 14 | content: string; 15 | 16 | side: MessageSide; 17 | 18 | participant: Participant; 19 | 20 | dialogueSettings: DialogueSettings; 21 | 22 | constructor(content: string, side: MessageSide, dialogueSettings: DialogueSettings) { 23 | this.content = content; 24 | this.side = side; 25 | this.dialogueSettings = dialogueSettings; 26 | 27 | this.participant = this.side == SIDES.LEFT ? this.dialogueSettings.leftParticipant : this.dialogueSettings.rightParticipant; 28 | 29 | this.renderMessage(); 30 | } 31 | 32 | renderMessage() { 33 | const messageEl = this.createMessageEl(); 34 | 35 | if (this.titleShouldRender()) { 36 | messageEl.createDiv({ cls: CLASSES.MESSAGE_TITLE, text: this.participant.title }); 37 | } 38 | 39 | messageEl.createDiv({ cls: CLASSES.MESSAGE_CONTENT, text: this.content }); 40 | } 41 | 42 | createMessageEl(): HTMLDivElement { 43 | const sideClass = this.side == SIDES.LEFT ? CLASSES.MESSAGE_WRAPPER_LEFT : CLASSES.MESSAGE_WRAPPER_RIGHT; 44 | const messageWrapperEl = this.dialogueSettings.parent.createDiv({ 45 | cls: `${CLASSES.BLOCK_WRAPPER} ${sideClass}` 46 | }); 47 | 48 | return messageWrapperEl.createDiv({ 49 | cls: CLASSES.MESSAGE, 50 | attr: { 51 | style: `max-width: ${this.dialogueSettings.messageMaxWidth};`, 52 | 'data-participant-name': this.participant.title, 53 | 'data-participant-id': this.participant.enforcedId ?? this.dialogueSettings.participants.get(this.participant.title) 54 | } 55 | }); 56 | } 57 | 58 | titleShouldRender(): boolean { 59 | if (this.participant.title.length < 1) return false; 60 | 61 | switch (this.dialogueSettings.titleMode) { 62 | case DialogueTitleMode.Disabled: return false; 63 | case DialogueTitleMode.All: return true; 64 | case DialogueTitleMode.First: { 65 | if (this.participant.renderedOnce) return false; 66 | 67 | this.participant.renderedOnce = true; 68 | return true; 69 | } 70 | default: return false; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/settings.ts: -------------------------------------------------------------------------------- 1 | import { App, Setting, PluginSettingTab } from 'obsidian'; 2 | import DialoguePlugin from './main'; 3 | import { DialogueTitleMode } from './types/dialogueTitleMode'; 4 | 5 | export interface DialoguePluginSettings { 6 | defaultLeftTitle: string; 7 | defaultRightTitle: string; 8 | defaultTitleMode: DialogueTitleMode; 9 | defaultMessageMaxWidth: string; 10 | defaultCommentMaxWidth: string; 11 | } 12 | 13 | export const DEFAULT_SETTINGS: DialoguePluginSettings = { 14 | defaultLeftTitle: '', 15 | defaultRightTitle: '', 16 | defaultTitleMode: DialogueTitleMode.First, 17 | defaultMessageMaxWidth: '60%', 18 | defaultCommentMaxWidth: '60%', 19 | } 20 | 21 | export class DialogueSettingTab extends PluginSettingTab { 22 | 23 | plugin: DialoguePlugin; 24 | 25 | constructor(app: App, plugin: DialoguePlugin) { 26 | super(app, plugin); 27 | this.plugin = plugin; 28 | } 29 | 30 | display(): void { 31 | const { containerEl } = this; 32 | 33 | containerEl.empty(); 34 | 35 | containerEl.createEl('h2', { text: 'Dialogue Settings' }); 36 | 37 | const coffeeEl = containerEl.createEl('div', { 38 | attr: { 39 | style: "text-align: center; margin-bottom: 10px;" 40 | } 41 | }); 42 | const coffeeLinkEl = coffeeEl.createEl('a', { href: "https://www.buymeacoffee.com/holubj" }); 43 | coffeeLinkEl.createEl('img', { 44 | attr: { 45 | src: "https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png", 46 | alt: "Buy Me A Coffee", 47 | style: "height: 60px; width: 217px;" 48 | } 49 | }); 50 | 51 | new Setting(containerEl) 52 | .setName('Default left title') 53 | .setDesc('Default value for left title in all dialogues.') 54 | .addText(text => 55 | text.setPlaceholder('Enter default left title') 56 | .setValue(this.plugin.settings.defaultLeftTitle) 57 | .onChange(async (value) => { 58 | this.plugin.settings.defaultLeftTitle = value; 59 | await this.plugin.saveSettings(); 60 | })); 61 | 62 | new Setting(containerEl) 63 | .setName('Default right title') 64 | .setDesc('Default value for right title in all dialogues.') 65 | .addText(text => 66 | text.setPlaceholder('Enter default right title') 67 | .setValue(this.plugin.settings.defaultRightTitle) 68 | .onChange(async (value) => { 69 | this.plugin.settings.defaultRightTitle = value; 70 | await this.plugin.saveSettings(); 71 | })); 72 | 73 | new Setting(containerEl) 74 | .setName('Default title mode') 75 | .setDesc('Default title mode in all dialogues.') 76 | .addDropdown(cb => { 77 | Object.values(DialogueTitleMode).forEach(titleMode => { 78 | const mode = titleMode.toString(); 79 | cb.addOption(mode, mode.charAt(0).toUpperCase() + mode.slice(1)); 80 | }); 81 | 82 | cb.setValue(this.plugin.settings.defaultTitleMode) 83 | .onChange(async (value) => { 84 | this.plugin.settings.defaultTitleMode = value as DialogueTitleMode; 85 | await this.plugin.saveSettings(); 86 | }); 87 | }); 88 | 89 | new Setting(containerEl) 90 | .setName('Default max message width') 91 | .setDesc('Default max message width in all dialogues.') 92 | .addText(text => 93 | text.setPlaceholder('Enter default max message width') 94 | .setValue(this.plugin.settings.defaultMessageMaxWidth) 95 | .onChange(async (value) => { 96 | this.plugin.settings.defaultMessageMaxWidth = value; 97 | await this.plugin.saveSettings(); 98 | })); 99 | 100 | new Setting(containerEl) 101 | .setName('Default max comment width') 102 | .setDesc('Default max comment width in all dialogues.') 103 | .addText(text => 104 | text.setPlaceholder('Enter default max comment width') 105 | .setValue(this.plugin.settings.defaultCommentMaxWidth) 106 | .onChange(async (value) => { 107 | this.plugin.settings.defaultCommentMaxWidth = value; 108 | await this.plugin.saveSettings(); 109 | })); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/dialogue.ts: -------------------------------------------------------------------------------- 1 | import { DialoguePluginSettings } from './settings'; 2 | import { DialogueTitleMode } from './types/dialogueTitleMode'; 3 | import { CLASSES } from './constants/classes'; 4 | import { Message, SIDES } from './components/message'; 5 | import { Delimiter } from './components/delimiter'; 6 | import { Comment } from './components/comment'; 7 | 8 | abstract class KEYWORDS { 9 | static readonly LEFT_PATTERN = /^l(?:eft)?(?:-(\d+))?:/i; 10 | static readonly RIGHT_PATTERN = /^r(?:ight)?(?:-(\d+))?:/i; 11 | static readonly TITLE_MODE = 'titleMode:'; 12 | static readonly MESSAGE_MAX_WIDTH = 'messageMaxWidth:'; 13 | static readonly COMMENT_MAX_WIDTH = 'commentMaxWidth:'; 14 | static readonly DELIMITER = /^-|delimiter/; 15 | static readonly COMMENT = '#'; 16 | static readonly MESSAGE_LEFT = '<'; 17 | static readonly MESSAGE_RIGHT = '>'; 18 | } 19 | 20 | export interface Participant { 21 | title: string; 22 | renderedOnce: boolean; 23 | enforcedId: string; 24 | } 25 | 26 | export interface DialogueSettings { 27 | parent: HTMLElement; 28 | leftParticipant: Participant; 29 | rightParticipant: Participant; 30 | titleMode: DialogueTitleMode; 31 | messageMaxWidth: string; 32 | commentMaxWidth: string; 33 | participants: Map; 34 | } 35 | 36 | export class DialogueRenderer { 37 | 38 | src: string; 39 | 40 | dialogueWrapperEl: HTMLElement; 41 | 42 | dialogueSettings: DialogueSettings; 43 | 44 | constructor(src: string, parent: HTMLElement, settings: DialoguePluginSettings) { 45 | this.src = src; 46 | 47 | this.dialogueWrapperEl = parent.createDiv({ cls: CLASSES.DIALOGUE_WRAPPER }); 48 | 49 | this.dialogueSettings = { 50 | parent: this.dialogueWrapperEl, 51 | leftParticipant: { 52 | title: settings.defaultLeftTitle, 53 | renderedOnce: false, 54 | enforcedId: null, 55 | }, 56 | rightParticipant: { 57 | title: settings.defaultRightTitle, 58 | renderedOnce: false, 59 | enforcedId: null, 60 | }, 61 | titleMode: settings.defaultTitleMode, 62 | messageMaxWidth: settings.defaultMessageMaxWidth, 63 | commentMaxWidth: settings.defaultCommentMaxWidth, 64 | participants: new Map(), 65 | } 66 | 67 | this.renderDialogue(); 68 | } 69 | 70 | registerParticipant(participant: string) { 71 | if (!this.dialogueSettings.participants.has(participant)) { 72 | this.dialogueSettings.participants.set(participant, this.dialogueSettings.participants.size + 1); // starting from number 1 73 | } 74 | } 75 | 76 | getEnforcedId(pattern: RegExp, line: string): string { 77 | let enforcedId = null; 78 | const result = pattern.exec(line); 79 | if (result != null && result.length > 1) { 80 | enforcedId = result[1]; 81 | } 82 | 83 | return enforcedId; 84 | } 85 | 86 | renderDialogue() { 87 | const lines = this.src 88 | .split(/\r?\n/) 89 | .map(line => line.trim()) 90 | .filter(line => line.length > 0); 91 | 92 | for (const line of lines) { 93 | 94 | if (KEYWORDS.LEFT_PATTERN.test(line)) { 95 | this.dialogueSettings.leftParticipant.title = line.split(':').splice(1).join(':').trim(); 96 | this.dialogueSettings.leftParticipant.renderedOnce = false; // reset this flag when a new title is set 97 | this.dialogueSettings.leftParticipant.enforcedId = this.getEnforcedId(KEYWORDS.LEFT_PATTERN, line); 98 | } 99 | else if (KEYWORDS.RIGHT_PATTERN.test(line)) { 100 | this.dialogueSettings.rightParticipant.title = line.split(':').splice(1).join(':').trim(); 101 | this.dialogueSettings.rightParticipant.renderedOnce = false; // reset this flag when a new title is set 102 | this.dialogueSettings.rightParticipant.enforcedId = this.getEnforcedId(KEYWORDS.RIGHT_PATTERN, line); 103 | } 104 | else if (line.startsWith(KEYWORDS.TITLE_MODE)) { 105 | const modeName = line.substr(KEYWORDS.TITLE_MODE.length).trim().toLowerCase(); 106 | if (Object.values(DialogueTitleMode).some(mode => mode == modeName)) { 107 | this.dialogueSettings.titleMode = modeName as DialogueTitleMode; 108 | } 109 | } 110 | else if (line.startsWith(KEYWORDS.MESSAGE_MAX_WIDTH)) { 111 | this.dialogueSettings.messageMaxWidth = line.substr(KEYWORDS.MESSAGE_MAX_WIDTH.length).trim(); 112 | } 113 | else if (line.startsWith(KEYWORDS.COMMENT_MAX_WIDTH)) { 114 | this.dialogueSettings.commentMaxWidth = line.substr(KEYWORDS.COMMENT_MAX_WIDTH.length).trim(); 115 | } 116 | else if (KEYWORDS.DELIMITER.test(line)) { 117 | new Delimiter(this.dialogueSettings); 118 | } 119 | else if (line.startsWith(KEYWORDS.COMMENT)) { 120 | const content = line.substr(KEYWORDS.COMMENT.length); 121 | 122 | new Comment(content, this.dialogueSettings); 123 | } 124 | else if (line.startsWith(KEYWORDS.MESSAGE_LEFT)) { 125 | const content = line.substr(KEYWORDS.MESSAGE_LEFT.length); 126 | this.registerParticipant(this.dialogueSettings.leftParticipant.title); 127 | 128 | new Message(content, SIDES.LEFT, this.dialogueSettings); 129 | } 130 | else if (line.startsWith(KEYWORDS.MESSAGE_RIGHT)) { 131 | const content = line.substr(KEYWORDS.MESSAGE_RIGHT.length); 132 | this.registerParticipant(this.dialogueSettings.rightParticipant.title); 133 | 134 | new Message(content, SIDES.RIGHT, this.dialogueSettings); 135 | } 136 | } 137 | } 138 | 139 | } 140 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Obsidian Dialogue Plugin 2 | 3 | Create dialogues in Markdown. 4 | 5 | ![dialogue](https://raw.githubusercontent.com/holubj/obsidian-dialogue-plugin/master/images/dialogue.png) 6 | 7 | ## Parameters 8 | 9 | Parameters can be set using commands inside the dialogue. All available parameters are listed in the table below. 10 | 11 | ### Available parameters 12 | 13 | | Parameter | Description | Default Value | 14 | | ------------------ | ----------------------------------------------------------------------------- | ------------- | 15 | | `left:` or `l:` | Name of the dialogue participant on the left side. | none | 16 | | `right:` or `r:` | Name of the dialogue participant on the right side. | none | 17 | | `titleMode:` | Defines if and when to render titles. See available modes in the table below. | `first` | 18 | | `messageMaxWidth:` | Defines the max message width in the dialogue. | `60%` | 19 | | `commentMaxWidth:` | Defines the max comment width in the dialogue. | `60%` | 20 | 21 | ### Title Modes 22 | 23 | | Mode | Description | 24 | | ---------- | ---------------------------------------------- | 25 | | `disabled` | Disable all titles. | 26 | | `first` | Render each title only on the first occurence. | 27 | | `all` | Always render title. | 28 | 29 | ## Usage 30 | 31 | ### Simple usage 32 | 33 | The message in the dialogue must be prefixed with 34 | 35 | - either `<` (message on the left side) 36 | - or `>` (message on the right side). 37 | 38 | The message must be exactly one paragraph. 39 | 40 | #### Example code 41 | 42 | ```` 43 | ```dialogue 44 | left: Ingmar Bergman 45 | right: Wong Kar-wai 46 | 47 | < Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec tristique nunc, et pharetra sem. 48 | < Nunc id auctor lectus, feugiat aliquet sem. 49 | 50 | > Lorem ipsum dolor sit amet 51 | > Ut nec efficitur mauris, a lacinia purus. Fusce nisi arcu, sollicitudin eget sodales sit amet, consectetur a lorem. Nam egestas tristique felis, sed suscipit nunc commodo nec. 52 | ``` 53 | ```` 54 | 55 | #### Result 56 | 57 | ![simple](https://raw.githubusercontent.com/holubj/obsidian-dialogue-plugin/master/images/simple.png) 58 | 59 | ### Advanced parameters 60 | 61 | All parameters listed in the table above can be used to customize the dialogue. 62 | 63 | #### Example code 64 | 65 | ```` 66 | ```dialogue 67 | left: Ingmar Bergman 68 | right: Wong Kar-wai 69 | titleMode: all 70 | messageMaxWidth: 40% 71 | 72 | < Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec tristique nunc, et pharetra sem. 73 | < Nunc id auctor lectus, feugiat aliquet sem. 74 | 75 | > Lorem ipsum dolor sit amet 76 | > Ut nec efficitur mauris, a lacinia purus. Fusce nisi arcu, sollicitudin eget sodales sit amet, consectetur a lorem. Nam egestas tristique felis, sed suscipit nunc commodo nec. 77 | ``` 78 | ```` 79 | 80 | #### Result 81 | 82 | ![parameters](https://raw.githubusercontent.com/holubj/obsidian-dialogue-plugin/master/images/parameters.png) 83 | 84 | ### Change of parameters during a dialogue 85 | 86 | Parameters can be modified during the dialogue (the change is applied to all following messages). 87 | 88 | #### Example code 89 | 90 | ```` 91 | ```dialogue 92 | left: Ingmar Bergman 93 | right: Wong Kar-wai 94 | 95 | < Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec tristique nunc, et pharetra sem. 96 | < Nunc id auctor lectus, feugiat aliquet sem. 97 | 98 | > Lorem ipsum dolor sit amet 99 | > Ut nec efficitur mauris, a lacinia purus. Fusce nisi arcu, sollicitudin eget sodales sit amet, consectetur a lorem. Nam egestas tristique felis, sed suscipit nunc commodo nec. 100 | 101 | left: Sion Sono 102 | 103 | < Nulla condimentum orci quis enim iaculis, ut congue turpis semper. Donec mattis elit vitae risus molestie vestibulum. 104 | < In laoreet aliquet neque, eget tempus massa congue ut. 105 | ``` 106 | ```` 107 | 108 | #### Result 109 | 110 | ![parameters2](https://raw.githubusercontent.com/holubj/obsidian-dialogue-plugin/master/images/parameters2.png) 111 | 112 | ### Dialogue with delimiter 113 | 114 | Use the `delimiter` (or shorter `-`) command to add a delimiter into the dialogue. 115 | 116 | #### Example code 117 | 118 | ```` 119 | ```dialogue 120 | left: Ingmar Bergman 121 | right: Wong Kar-wai 122 | 123 | < Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec tristique nunc, et pharetra sem. 124 | < Nunc id auctor lectus, feugiat aliquet sem. 125 | 126 | delimiter 127 | 128 | > Lorem ipsum dolor sit amet 129 | > Ut nec efficitur mauris, a lacinia purus. Fusce nisi arcu, sollicitudin eget sodales sit amet, consectetur a lorem. Nam egestas tristique felis, sed suscipit nunc commodo nec. 130 | ``` 131 | ```` 132 | 133 | #### Result 134 | 135 | ![delimiter](https://raw.githubusercontent.com/holubj/obsidian-dialogue-plugin/master/images/delimiter.png) 136 | 137 | ### Dialogue with comments 138 | 139 | Comments can be added into the dialogue with `#` prefix (see example below). The comment must be exactly one paragraph. 140 | Max width of the comments can be modified with the `commentMaxWidth:` parameter. 141 | 142 | #### Example code 143 | 144 | ```` 145 | ```dialogue 146 | left: Ingmar Bergman 147 | right: Wong Kar-wai 148 | 149 | < Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean nec tristique nunc, et pharetra sem. 150 | < Nunc id auctor lectus, feugiat aliquet sem. 151 | 152 | # Lorem ipsum dolor sit amet 153 | 154 | > Lorem ipsum dolor sit amet 155 | 156 | # Vivamus nunc orci, aliquet varius rutrum et, pulvinar vitae nunc. Pellentesque a consequat ipsum. 157 | 158 | > Ut nec efficitur mauris, a lacinia purus. Fusce nisi arcu, sollicitudin eget sodales sit amet, consectetur a lorem. Nam egestas tristique felis, sed suscipit nunc commodo nec. 159 | ``` 160 | ```` 161 | 162 | #### Result 163 | 164 | ![comments](https://raw.githubusercontent.com/holubj/obsidian-dialogue-plugin/master/images/comments.png) 165 | 166 | ## Custom styles for messages 167 | 168 | Messages have special `data` attributes to allow custom styling. 169 | 170 | Each message has: 171 | 172 | - `data-participant-id` attribute with a unique numeric id as a value to identify the same dialogue participant (in order of appearance, starting from number 1) 173 | - `data-participant-name` attribute with a name of the dialogue participant as a value 174 | 175 | These attributes can be used in a **CSS snippet** to apply custom styles to messages based on the message author. See example below. 176 | 177 | ### Specifying custom id for a dialogue particiant 178 | 179 | To specify a custom id for a participant in the dialogue (for example if you want to have the same color for the selected participant across multiple dialogues), you can do it by appending the id to the participant definition (for example `left-2: Name`, where `2` is your id). 180 | 181 | ### Styling example 182 | 183 | This example sets selected background colors for the first three unique dialogue participants (in order of appearance) and also one specific color for a dialogue participant named `Sion Sono`. 184 | 185 | ```css 186 | /* messages from first dialogue participant will have #f00 background color */ 187 | .dialogue-plugin-message[data-participant-id="1"] { 188 | background-color: #f00; 189 | } 190 | 191 | /* messages from second dialogue participant will have #0f0 background color */ 192 | .dialogue-plugin-message[data-participant-id="2"] { 193 | background-color: #0f0; 194 | } 195 | 196 | /* messages from third dialogue participant will have #00f background color */ 197 | .dialogue-plugin-message[data-participant-id="3"] { 198 | background-color: #00f; 199 | } 200 | 201 | /* messages from dialogue participant named 'Sion Sono' will have #f0f background color */ 202 | .dialogue-plugin-message[data-participant-name="Sion Sono"] { 203 | background-color: #f0f; 204 | } 205 | ``` 206 | 207 | ## Say Thanks 🙏 208 | 209 | If you like this plugin and would like to support its development, you can buy me a coffee! 210 | 211 | Buy Me A Coffee 212 | --------------------------------------------------------------------------------