mirror of
https://github.com/ozntel/oz-calendar.git
synced 2026-07-22 07:40:24 +00:00
New Note Option, Default Folder
This commit is contained in:
parent
07057143c0
commit
8f1bda2d08
10 changed files with 351 additions and 4004 deletions
3991
package-lock.json
generated
3991
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -12,6 +12,7 @@
|
|||
"author": "",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/common-tags": "^1.8.1",
|
||||
"@types/node": "^16.11.6",
|
||||
"@types/react": "17.0.2",
|
||||
"@types/react-calendar": "^3.9.0",
|
||||
|
|
@ -26,6 +27,8 @@
|
|||
"typescript": "4.7.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.11.6",
|
||||
"common-tags": "^1.8.2",
|
||||
"dayjs": "1.11.7",
|
||||
"preact": "10",
|
||||
"react": "npm:@preact/compat@17.0.2",
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import React, { useMemo, useState } from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { BsArrowRight, BsArrowLeft } from 'react-icons/bs';
|
||||
import { HiOutlineDocumentText } from 'react-icons/hi';
|
||||
import { RiPhoneFindLine } from 'react-icons/ri';
|
||||
import { RiPhoneFindLine, RiAddCircleLine } from 'react-icons/ri';
|
||||
import dayjs from 'dayjs';
|
||||
import OZCalendarPlugin from '../main';
|
||||
import OZCalendarPlugin from 'main';
|
||||
import { openFile } from '../util/utils';
|
||||
import { TFile } from 'obsidian';
|
||||
import { CreateNoteModal } from 'modal';
|
||||
|
||||
interface NoteListComponentParams {
|
||||
selectedDay: Date;
|
||||
|
|
@ -53,6 +54,15 @@ export default function NoteListComponent(params: NoteListComponentParams) {
|
|||
return (
|
||||
<>
|
||||
<div className="oz-calendar-notelist-header-container">
|
||||
<div className="oz-calendar-nav-action-plus">
|
||||
<RiAddCircleLine
|
||||
size={20}
|
||||
onClick={() => {
|
||||
let newFileModal = new CreateNoteModal(plugin, selectedDay);
|
||||
newFileModal.open();
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="oz-calendar-nav-action-left">
|
||||
<BsArrowLeft size={22} onClick={() => setNewSelectedDay(-1)} />
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import { CachedMetadata, Plugin, TAbstractFile, TFile, addIcon } from 'obsidian';
|
||||
import { OZCalendarView, VIEW_TYPE } from './view';
|
||||
import { OZCalendarView, VIEW_TYPE } from 'view';
|
||||
import dayjs from 'dayjs';
|
||||
import { OZCalendarDaysMap } from './types';
|
||||
import { OZCalendarDaysMap } from 'types';
|
||||
import { OZCAL_ICON } from './util/icons';
|
||||
import { OZCalendarPluginSettings, DEFAULT_SETTINGS, OZCalendarPluginSettingsTab } from 'settings';
|
||||
import { OZCalendarPluginSettings, DEFAULT_SETTINGS, OZCalendarPluginSettingsTab } from './settings/settings';
|
||||
|
||||
export default class OZCalendarPlugin extends Plugin {
|
||||
settings: OZCalendarPluginSettings;
|
||||
|
|
|
|||
68
src/modal.ts
Normal file
68
src/modal.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import OZCalendarPlugin from 'main';
|
||||
import { Modal, TFolder, Notice } from 'obsidian';
|
||||
import { createNewMarkdownFile } from './util/utils';
|
||||
import { stripIndents } from 'common-tags';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export class CreateNoteModal extends Modal {
|
||||
plugin: OZCalendarPlugin;
|
||||
selectedDay: Date;
|
||||
|
||||
constructor(plugin: OZCalendarPlugin, selectedDay: Date) {
|
||||
super(plugin.app);
|
||||
this.plugin = plugin;
|
||||
this.selectedDay = selectedDay;
|
||||
}
|
||||
|
||||
onOpen(): void {
|
||||
let { contentEl } = this;
|
||||
let thisModal = this;
|
||||
|
||||
const headerEl = contentEl.createEl('div', { text: 'Create Note: Provide Name' });
|
||||
headerEl.addClass('modal-title');
|
||||
|
||||
// Input El
|
||||
const inputEl = contentEl.createEl('input');
|
||||
inputEl.style.cssText = 'width: 100%; height: 2.5em; margin-bottom: 15px;';
|
||||
|
||||
inputEl.focus();
|
||||
|
||||
const createButton = contentEl.createEl('button', { text: 'Create Note' });
|
||||
|
||||
const cancelButton = contentEl.createEl('button', { text: 'Cancel' });
|
||||
cancelButton.style.cssText = 'float: right;';
|
||||
cancelButton.addEventListener('click', () => {
|
||||
thisModal.close();
|
||||
});
|
||||
|
||||
let defaultNewFileText = stripIndents`
|
||||
---
|
||||
${this.plugin.settings.yamlKey}: ${dayjs().format(this.plugin.settings.dateFormat)}
|
||||
---
|
||||
`;
|
||||
|
||||
const onClickCreateButton = async () => {
|
||||
let newFileName = inputEl.value;
|
||||
if (newFileName !== '') {
|
||||
let defFolderSrc = this.plugin.settings.defaultFolder;
|
||||
let defFolder = this.app.vault.getAbstractFileByPath(defFolderSrc);
|
||||
if (defFolder && defFolder instanceof TFolder) {
|
||||
await createNewMarkdownFile(this.plugin, defFolder, newFileName, defaultNewFileText);
|
||||
thisModal.close();
|
||||
}
|
||||
} else {
|
||||
new Notice('You didnt provide file name');
|
||||
}
|
||||
};
|
||||
|
||||
createButton.addEventListener('click', onClickCreateButton);
|
||||
inputEl.addEventListener('keydown', async (e) => {
|
||||
if (e.key === 'Enter') await onClickCreateButton();
|
||||
});
|
||||
}
|
||||
|
||||
onClose() {
|
||||
let { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,19 @@
|
|||
import OZCalendarPlugin from 'main';
|
||||
import { PluginSettingTab, App, Setting } from 'obsidian';
|
||||
import { FolderSuggest } from 'settings/suggestor';
|
||||
|
||||
export interface OZCalendarPluginSettings {
|
||||
openViewOnStart: boolean;
|
||||
yamlKey: string;
|
||||
dateFormat: string;
|
||||
defaultFolder: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: OZCalendarPluginSettings = {
|
||||
openViewOnStart: true,
|
||||
yamlKey: 'created',
|
||||
dateFormat: 'YYYY-MM-DD hh:mm:ss',
|
||||
defaultFolder: '/',
|
||||
};
|
||||
|
||||
export class OZCalendarPluginSettingsTab extends PluginSettingTab {
|
||||
|
|
@ -90,5 +93,20 @@ export class OZCalendarPluginSettingsTab extends PluginSettingTab {
|
|||
this.plugin.reloadPlugin();
|
||||
});
|
||||
});
|
||||
|
||||
new Setting(this.containerEl)
|
||||
.setName('Default Folder Location')
|
||||
.setDesc('Select the defaukt folder, under which the new files should be saved')
|
||||
.addSearch((cb) => {
|
||||
new FolderSuggest(cb.inputEl);
|
||||
cb.setPlaceholder('Example: folder1/folder2')
|
||||
.setValue(this.plugin.settings.defaultFolder)
|
||||
.onChange((new_folder) => {
|
||||
this.plugin.settings.defaultFolder = new_folder;
|
||||
this.plugin.saveSettings();
|
||||
});
|
||||
// @ts-ignore
|
||||
cb.containerEl.addClass('templater_search');
|
||||
});
|
||||
}
|
||||
}
|
||||
182
src/settings/suggest.ts
Normal file
182
src/settings/suggest.ts
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
|
||||
|
||||
import { ISuggestOwner, Scope } from 'obsidian';
|
||||
import { createPopper, Instance as PopperInstance } from '@popperjs/core';
|
||||
|
||||
const wrapAround = (value: number, size: number): number => {
|
||||
return ((value % size) + size) % size;
|
||||
};
|
||||
|
||||
class Suggest<T> {
|
||||
private owner: ISuggestOwner<T>;
|
||||
private values: T[];
|
||||
private suggestions: HTMLDivElement[];
|
||||
private selectedItem: number;
|
||||
private containerEl: HTMLElement;
|
||||
|
||||
constructor(owner: ISuggestOwner<T>, containerEl: HTMLElement, scope: Scope) {
|
||||
this.owner = owner;
|
||||
this.containerEl = containerEl;
|
||||
|
||||
containerEl.on('click', '.suggestion-item', this.onSuggestionClick.bind(this));
|
||||
containerEl.on('mousemove', '.suggestion-item', this.onSuggestionMouseover.bind(this));
|
||||
|
||||
scope.register([], 'ArrowUp', (event) => {
|
||||
if (!event.isComposing) {
|
||||
this.setSelectedItem(this.selectedItem - 1, true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
scope.register([], 'ArrowDown', (event) => {
|
||||
if (!event.isComposing) {
|
||||
this.setSelectedItem(this.selectedItem + 1, true);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
scope.register([], 'Enter', (event) => {
|
||||
if (!event.isComposing) {
|
||||
this.useSelectedItem(event);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onSuggestionClick(event: MouseEvent, el: HTMLDivElement): void {
|
||||
event.preventDefault();
|
||||
|
||||
const item = this.suggestions.indexOf(el);
|
||||
this.setSelectedItem(item, false);
|
||||
this.useSelectedItem(event);
|
||||
}
|
||||
|
||||
onSuggestionMouseover(_event: MouseEvent, el: HTMLDivElement): void {
|
||||
const item = this.suggestions.indexOf(el);
|
||||
this.setSelectedItem(item, false);
|
||||
}
|
||||
|
||||
setSuggestions(values: T[]) {
|
||||
this.containerEl.empty();
|
||||
const suggestionEls: HTMLDivElement[] = [];
|
||||
|
||||
values.forEach((value) => {
|
||||
const suggestionEl = this.containerEl.createDiv('suggestion-item');
|
||||
this.owner.renderSuggestion(value, suggestionEl);
|
||||
suggestionEls.push(suggestionEl);
|
||||
});
|
||||
|
||||
this.values = values;
|
||||
this.suggestions = suggestionEls;
|
||||
this.setSelectedItem(0, false);
|
||||
}
|
||||
|
||||
useSelectedItem(event: MouseEvent | KeyboardEvent) {
|
||||
const currentValue = this.values[this.selectedItem];
|
||||
if (currentValue) {
|
||||
this.owner.selectSuggestion(currentValue, event);
|
||||
}
|
||||
}
|
||||
|
||||
setSelectedItem(selectedIndex: number, scrollIntoView: boolean) {
|
||||
const normalizedIndex = wrapAround(selectedIndex, this.suggestions.length);
|
||||
const prevSelectedSuggestion = this.suggestions[this.selectedItem];
|
||||
const selectedSuggestion = this.suggestions[normalizedIndex];
|
||||
|
||||
prevSelectedSuggestion?.removeClass('is-selected');
|
||||
selectedSuggestion?.addClass('is-selected');
|
||||
|
||||
this.selectedItem = normalizedIndex;
|
||||
|
||||
if (scrollIntoView) {
|
||||
selectedSuggestion.scrollIntoView(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export abstract class TextInputSuggest<T> implements ISuggestOwner<T> {
|
||||
protected inputEl: HTMLInputElement | HTMLTextAreaElement;
|
||||
|
||||
private popper: PopperInstance;
|
||||
private scope: Scope;
|
||||
private suggestEl: HTMLElement;
|
||||
private suggest: Suggest<T>;
|
||||
|
||||
constructor(inputEl: HTMLInputElement | HTMLTextAreaElement) {
|
||||
this.inputEl = inputEl;
|
||||
this.scope = new Scope();
|
||||
|
||||
this.suggestEl = createDiv('suggestion-container');
|
||||
const suggestion = this.suggestEl.createDiv('suggestion');
|
||||
this.suggest = new Suggest(this, suggestion, this.scope);
|
||||
|
||||
this.scope.register([], 'Escape', this.close.bind(this));
|
||||
|
||||
this.inputEl.addEventListener('input', this.onInputChanged.bind(this));
|
||||
this.inputEl.addEventListener('focus', this.onInputChanged.bind(this));
|
||||
this.inputEl.addEventListener('blur', this.close.bind(this));
|
||||
this.suggestEl.on('mousedown', '.suggestion-container', (event: MouseEvent) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
}
|
||||
|
||||
onInputChanged(): void {
|
||||
const inputStr = this.inputEl.value;
|
||||
const suggestions = this.getSuggestions(inputStr);
|
||||
|
||||
if (!suggestions) {
|
||||
this.close();
|
||||
return;
|
||||
}
|
||||
|
||||
if (suggestions.length > 0) {
|
||||
this.suggest.setSuggestions(suggestions);
|
||||
// @ts-ignore
|
||||
this.open(app.dom.appContainerEl, this.inputEl);
|
||||
} else {
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
||||
open(container: HTMLElement, inputEl: HTMLElement): void {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
app.keymap.pushScope(this.scope);
|
||||
|
||||
container.appendChild(this.suggestEl);
|
||||
this.popper = createPopper(inputEl, this.suggestEl, {
|
||||
placement: 'bottom-start',
|
||||
modifiers: [
|
||||
{
|
||||
name: 'sameWidth',
|
||||
enabled: true,
|
||||
fn: ({ state, instance }) => {
|
||||
// Note: positioning needs to be calculated twice -
|
||||
// first pass - positioning it according to the width of the popper
|
||||
// second pass - position it with the width bound to the reference element
|
||||
// we need to early exit to avoid an infinite loop
|
||||
const targetWidth = `${state.rects.reference.width}px`;
|
||||
if (state.styles.popper.width === targetWidth) {
|
||||
return;
|
||||
}
|
||||
state.styles.popper.width = targetWidth;
|
||||
instance.update();
|
||||
},
|
||||
phase: 'beforeWrite',
|
||||
requires: ['computeStyles'],
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
close(): void {
|
||||
app.keymap.popScope(this.scope);
|
||||
|
||||
this.suggest.setSuggestions([]);
|
||||
if (this.popper) this.popper.destroy();
|
||||
this.suggestEl.detach();
|
||||
}
|
||||
|
||||
abstract getSuggestions(inputStr: string): T[];
|
||||
abstract renderSuggestion(item: T, el: HTMLElement): void;
|
||||
abstract selectSuggestion(item: T): void;
|
||||
}
|
||||
30
src/settings/suggestor.ts
Normal file
30
src/settings/suggestor.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
// Credits go to Liam's Periodic Notes Plugin: https://github.com/liamcain/obsidian-periodic-notes
|
||||
|
||||
import { TAbstractFile, TFolder } from 'obsidian';
|
||||
import { TextInputSuggest } from './suggest';
|
||||
|
||||
export class FolderSuggest extends TextInputSuggest<TFolder> {
|
||||
getSuggestions(inputStr: string): TFolder[] {
|
||||
const abstractFiles = app.vault.getAllLoadedFiles();
|
||||
const folders: TFolder[] = [];
|
||||
const lowerCaseInputStr = inputStr.toLowerCase();
|
||||
|
||||
abstractFiles.forEach((folder: TAbstractFile) => {
|
||||
if (folder instanceof TFolder && folder.path.toLowerCase().contains(lowerCaseInputStr)) {
|
||||
folders.push(folder);
|
||||
}
|
||||
});
|
||||
|
||||
return folders;
|
||||
}
|
||||
|
||||
renderSuggestion(file: TFolder, el: HTMLElement): void {
|
||||
el.setText(file.path);
|
||||
}
|
||||
|
||||
selectSuggestion(file: TFolder): void {
|
||||
this.inputEl.value = file.path;
|
||||
this.inputEl.trigger('input');
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { TFile } from 'obsidian';
|
||||
import { TFile, TFolder } from 'obsidian';
|
||||
import OZCalendarPlugin from '../main';
|
||||
|
||||
/**
|
||||
|
|
@ -17,3 +17,15 @@ export const openFile = (params: {
|
|||
plugin.app.workspace.setActiveLeaf(leaf, { focus: true });
|
||||
leaf.openFile(file, { eState: { focus: true } });
|
||||
};
|
||||
|
||||
export const createNewMarkdownFile = async (
|
||||
plugin: OZCalendarPlugin,
|
||||
folder: TFolder,
|
||||
newFileName: string,
|
||||
content?: string
|
||||
) => {
|
||||
// @ts-ignore
|
||||
const newFile = await plugin.app.fileManager.createNewMarkdownFile(folder, newFileName);
|
||||
if (content && content !== '') await plugin.app.vault.modify(newFile, content);
|
||||
openFile({ file: newFile, plugin: plugin, newLeaf: false });
|
||||
};
|
||||
|
|
|
|||
27
styles.css
27
styles.css
|
|
@ -101,15 +101,29 @@
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.oz-calendar-nav-action-plus {
|
||||
width: 10%;
|
||||
display: inline-block;
|
||||
color: var(--text-muted);
|
||||
vertical-align: top;
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.oz-calendar-nav-action-plus svg:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.oz-calendar-nav-action-left {
|
||||
display: inline-block;
|
||||
width: 20%;
|
||||
width: 15%;
|
||||
color: var(--text-muted);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.oz-calendar-nav-action-middle {
|
||||
display: inline-block;
|
||||
width: 60%;
|
||||
width: 50%;
|
||||
text-align: center;
|
||||
vertical-align: top;
|
||||
font-size: 1.1em;
|
||||
|
|
@ -119,16 +133,15 @@
|
|||
|
||||
.oz-calendar-nav-action-right {
|
||||
display: inline-block;
|
||||
width: 20%;
|
||||
align-items: end;
|
||||
text-align: right;
|
||||
width: 25%;
|
||||
text-align: left;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.oz-calendar-nav-action-left svg:hover,
|
||||
.oz-calendar-nav-action-right svg:hover {
|
||||
cursor: pointer;
|
||||
opacity: 0.8;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.oz-calendar-notelist-container {
|
||||
|
|
@ -144,6 +157,8 @@
|
|||
|
||||
.oz-calendar-note-line:hover {
|
||||
cursor: pointer;
|
||||
background-color: var(--background-secondary-alt);
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.oz-calendar-note-line-icon {
|
||||
|
|
|
|||
Loading…
Reference in a new issue