Plugin: CodeScript Toolkit

(formerly known as Fix Require Modules)

This plugin is meant for the developers and other code writers. If you find the purpose of the plugin is not clear enough, I would be more than welcome to accept the PR to improve the README

2 Likes

Started porting my Templater scripts and User Plugins js scripts over to be used by your plugin today.

Reasons for doing so:

  • Templater’s annoying ‘Create’ and ‘Insert’ duplications. I always want to use ‘Insert’ and have to double check which one to fire when script is not among those added a key combo (running out of combos on Windows too) or not pinned on Command Palette (I don’t like it too long).
    • I also was in the habit of naming ‘Template’ my Templater scripts that are not even Templates – what a stupid thing to do – enough is enough…
  • User Plugins have some quirks and bugs I am tired of working around or unable to do so.
  • Too lazy to compile my own plugins and I certainly don’t want to take on 20-30 extra plugins when they are scripts doing usually simple enough custom stuff only.

On a personal note, Mikhael is one of my most liked developers, not only for his intuitive solutions, but his all-around availability.

I recommend checking out his other actively maintained plugins as well.

Cheers

1 Like

It is possible to avoid duplication between Create and Insert using one template with according check

Not sure I follow you. A conditional? How would Templater know what I would want to use the script as? Never heard of this.

This is how I differentiate them:
{48FFE2A1-C1EC-41F7-9033-E04FC643F717}
So I can see the ones with the (not yet learnt shortcut), that they are the Insert ones.

You are right. I confused with another thing I used. Sorry

1 Like

I was not very keen to use Templater as I didn’t want to take on a liability and Obsidian API is enough.
But I was still trying to find a way to use the Templater functionality.
Once I managed to find a solution with Claude (VSCode Copilot), I had it write up a guide.

@mnaoumov Please comment if you find anything wrong in it:

Guide: Adding Templater Functionality to TypeScript Scripts

Key Components:
  1. Interface Definition

Here’s a comprehensive list of available Templater functions we can add to the interface:

interface TemplaterPlugin {
    templater: {
        current_functions_object: {
            app: any;
            date: {
                now: (format: string) => Promise<string>;
                tomorrow: (format?: string) => Promise<string>;
                yesterday: (format?: string) => Promise<string>;
                weekday: (format?: string, weekday?: number) => Promise<string>;
            };
            file: {
                path: (full?: boolean) => string;
                folder: (full?: boolean) => string;
                title: string;
                selection: () => string;
                tags: string[];
                creation_date: (format?: string) => Promise<string>;
                last_modified_date: (format?: string) => Promise<string>;
                move: (path: string) => Promise<void>;
                rename: (newName: string) => Promise<void>;
                find_tfile: (path: string) => TFile | null;
                include: (path: string) => Promise<string>;
                cursor: (order?: number) => number;
                cursor_append: (content: string) => void;
                exists: (path: string) => boolean;
                create_new: (template: string, filename: string, open?: boolean) => Promise<void>;
            };
            frontmatter: {
                [key: string]: any;
            };
            system: {
                clipboard: () => Promise<string>;
                prompt: (prompt_text: string, default_value?: string, throw_on_cancel?: boolean) => Promise<string>;
                suggester: <T>(
                    text_items: ((item: T) => string) | string[],
                    items: T[],
                    throw_on_cancel?: boolean,
                    placeholder?: string,
                    limit?: boolean
                ) => Promise<T>;
            };
            web: {
                daily_quote: () => Promise<string>;
                random_picture: () => Promise<string>;
                request: (url: string) => Promise<any>;
            };
            obsidian: {
                // Access to Obsidian API
                [key: string]: any;
            };
            user: {
                // Custom user scripts
                [key: string]: any;
            };
        };
    };
}

Key Functions Available:

  1. Date Functions:

    • now() - Current date/time
    • tomorrow() - Tomorrow’s date
    • yesterday() - Yesterday’s date
    • weekday() - Get specific weekday
  2. File Operations:

    • path() - Get file path
    • title - Get file title
    • selection() - Get selected text
    • move() - Move file
    • rename() - Rename file
    • create_new() - Create new file
  3. System Interactions:

    • clipboard() - Get clipboard content
    • prompt() - Show user prompt
    • suggester() - Show selection menu
  4. Web Functions:

    • daily_quote() - Get daily quote
    • random_picture() - Get random picture
    • request() - Make web requests
  5. Meta Functions:

    • Access to frontmatter
    • Access to Obsidian API
    • Custom user scripts

Example Usage:

// Get user input
const userInput = await tp.system.prompt("Enter value");

// Use suggester for selection
const selected = await tp.system.suggester(
    items => items.display,
    items
);

// Include another template
const content = await tp.file.include("path/to/template.md");

// Create new file from template
await tp.file.create_new("template.md", "New File", true);
  1. Accessing Templater
const templaterPlugin = app.plugins.plugins['templater-obsidian'] as any;
const tp = templaterPlugin?.templater?.current_functions_object;

// Check availability
if (!tp?.date?.now) {
    console.error("Required Templater functions not found!");
    return;
}
Common Templater Functions:
// Dates
const getDate = async () => await tp.date.now("YYYY-MM-DD[T]HH:mm");

// Files
const getCurrentFile = () => tp.file.find_tfile(tp.file.path(true));

// System
const userInput = await tp.system.prompt("Enter value");
const selected = await tp.system.suggester(
    items => items.display, 
    items
);
Best Practices:
  1. Always wrap in error handling:
try {
    const date = await tp.date.now(format);
} catch (error) {
    console.error("Templater error:", error);
    // Provide fallback
    return moment().format(format);
}
  1. Check for plugin availability:
if (!app.plugins.plugins['templater-obsidian']) {
    console.error("Templater plugin not found!");
    return;
}
  1. Use Promise wrapper for mobile compatibility:
return new Promise<void>(async (resolve) => {
    try {
        // Templater code here
    } catch (error) {
        console.error('Error:', error);
    } finally {
        resolve();
    }
});
Example Template:
import { App, Plugin, TFile } from 'obsidian';

interface TemplaterPlugin {
    templater: {
        current_functions_object: {
            // Add needed functions
        };
    };
}

async function yourFunction(app: App): Promise<void> {
    return new Promise<void>(async (resolve) => {
        try {
            const templaterPlugin = app.plugins.plugins['templater-obsidian'] as any;
            const tp = templaterPlugin?.templater?.current_functions_object;
            
            if (!tp) {
                console.error("Templater not found!");
                return resolve();
            }

            // Your code using tp functions

        } catch (error) {
            console.error('Error:', error);
        } finally {
            resolve();
        }
    });
}

// ...existing Plugin class and invoke function...
Common Gotchas:

Templater functions are async - use await
Functions might be unavailable - always check
Mobile needs Promise wrapper
Use fallbacks for critical functions

@Yurcee there are many places that I would consider wrong here

  1. Typings of TemplaterPlugin are not correct. I will provide the correct typings in a bit
  2. You are not using those typings later on
const templaterPlugin = app.plugins.plugins['templater-obsidian'] as TemplaterPlugin;
  1. I would not recommend using TemplaterPlugin outside of Templater. For example, it won’t be initialized if you try to use it before any templates were running.

  2. Use Promise wrapper for mobile compatibility is an anti-pattern, it’s quite bad to use async inside Promise constructor

The correct way is

const asyncFn = async () => {
  // ...
};
const promise = asyncFn();
return promise;
1 Like
import * as obsidian from 'obsidian';
import {
  App,
  TFile,
  TFolder,
} from 'obsidian';

enum RunMode {
  CreateNewFromTemplate,
  AppendActiveFile,
  OverwriteFile,
  OverwriteActiveFile,
  DynamicProcessor,
  StartupTemplate,
}

interface TemplaterPlugin {
  templater: {
    current_functions_object: {
      app: App;
      config: {
        active_file: TFile;
        run_mode: RunMode;
        target_file: TFile;
        template_file: TFile;
      };
      date: {
        // default format is 'YYYY-MM-DD'
        now(format?: string, offset?: number | string, reference?: string, reference_format?: string): string;
        tomorrow(format?: string): string;
        weekday(format?: string, weekday?: number, reference?: string, reference_format?: string): string;
        yesterday(format?: string): string;
      };
      file: {
        content: string;
        tags: string[];
        title: string;
        create_new(template: TFile | string, filename?: string, open_new?: boolean, folder?: TFolder | string): Promise<TFile | undefined>;
        // default format is 'YYYY-MM-DD HH:mm'
        creation_date(format?: string): string;
        cursor(order?: number): string;
        cursor_append(content: string): string | undefined;
        exists(filepath: string): Promise<boolean>;
        find_tfile(filename: string): TFile | null;
        folder(absolute?: boolean): string;
        include(include_link: string | TFile): Promise<string>;
        // default format is 'YYYY-MM-DD HH:mm'
        last_modified_date(format: string): string;
        move(new_path: string, file_to_move?: TFile): Promise<string>;
        path(relative: boolean): string;
        rename(new_title: string): Promise<string>;
        selection(): string;
      };
      frontmatter: {
        [key: string]: unknown;
      };
      hooks: {
        on_all_templates_executed(callback_function: () => unknown): void;
      };
      obsidian: typeof obsidian;
      system: {
        clipboard(): Promise<string | null>;
        prompt(prompt_text?: string, default_value?: string, throw_on_cancel?: boolean, multiline?: boolean): Promise<string | null>;
        suggester<T>(text_items: string[] | ((item: T) => string), items: T[], throw_on_cancel?: boolean, placeholder?: string, limit?: number): Promise<T>;
      };
      user: {
        [key: string]: unknown;
      };
      web: {
        daily_quote(): Promise<string>;
        random_picture(size?: string, query?: string, include_size?: boolean): Promise<string>;
        request(url: string, path?: string): Promise<string>;
      };
    };
  };
}
1 Like

Yeah, that’s why an empty Templater template is advisable to run for this purpose on startup. I mean even for normal js scripts, for Templater function availability.

Then I’ll need to fix that for mobile compatibility even for scripts I didn’t use Templater for (so far only one and I kept the non-Templater version ts file for backup). That’s important, yes, even for Templater js.

Thanks very much for commenting on this on short notice.

Cheers

1 Like

As far as I can see, currently, only one startup script is allowed:

Would be better (if it’s possbile) if we just specified the folder and anything in that folder would be loaded on startup.
(My example scripts on the screenshot are not actual startup files – I just added two random files there.)

Yes, you have only one startup script, but you can call from it all others

startup/main.ts

import { invoke as freezeDataView } from './Freeze DataView.ts';
import { invoke as redoLastCommand } from './RedoLastCommand.ts';

export async function invoke(app: App): Promise<void> {
  await freezeDataView(app);
  await redoLastCommand(app);
}
1 Like

Will do, thanks!

This way, you will have a better control over the execution order.

And additionally, this forum is not the best place for the BUG/FEATURE REQUESTS. For better maintainability, they are better be added to GitHub

1 Like

Right.
Was just trying to raise awareness of the plugin a bit more.

Cheers