mirror of
https://github.com/d7sd6u/obsidian-crosslink-advanced.git
synced 2026-07-22 05:41:20 +00:00
Init
This commit is contained in:
commit
17dfc102db
22 changed files with 2251 additions and 0 deletions
10
.editorconfig
Normal file
10
.editorconfig
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
# top-most EditorConfig file
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
end_of_line = lf
|
||||||
|
insert_final_newline = true
|
||||||
|
indent_style = tab
|
||||||
|
indent_size = 4
|
||||||
|
tab_width = 4
|
||||||
1
.envrc
Normal file
1
.envrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
use flake
|
||||||
29
.gitignore
vendored
Normal file
29
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
# vscode
|
||||||
|
.vscode
|
||||||
|
.home
|
||||||
|
|
||||||
|
# Intellij
|
||||||
|
*.iml
|
||||||
|
.idea
|
||||||
|
|
||||||
|
main.js
|
||||||
|
dist
|
||||||
|
|
||||||
|
# npm
|
||||||
|
node_modules
|
||||||
|
node_modules.bak
|
||||||
|
.direnv
|
||||||
|
test
|
||||||
|
|
||||||
|
# Don't include the compiled main.js file in the repo.
|
||||||
|
# They should be uploaded to GitHub releases instead.
|
||||||
|
main.js
|
||||||
|
|
||||||
|
# Exclude sourcemaps
|
||||||
|
*.map
|
||||||
|
|
||||||
|
# obsidian
|
||||||
|
data.json
|
||||||
|
|
||||||
|
# Exclude macOS Finder (System Explorer) View States
|
||||||
|
.DS_Store
|
||||||
6
.gitmodules
vendored
Normal file
6
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
[submodule "obsidian-typings"]
|
||||||
|
path = obsidian-typings
|
||||||
|
url = git@github.com:d7sd6u/obsidian-typings.git
|
||||||
|
[submodule "obsidian-reusables"]
|
||||||
|
path = obsidian-reusables
|
||||||
|
url = git@github.com:d7sd6u/obsidian-reusables.git
|
||||||
1
.npmrc
Normal file
1
.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
tag-version-prefix=""
|
||||||
6
.prettierignore
Normal file
6
.prettierignore
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
obsidian-typings
|
||||||
|
node_modules
|
||||||
|
.direnv
|
||||||
|
dist
|
||||||
|
pnpm-lock.yaml
|
||||||
|
flake.lock
|
||||||
165
LICENSE.md
Normal file
165
LICENSE.md
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
GNU LESSER GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
|
||||||
|
This version of the GNU Lesser General Public License incorporates
|
||||||
|
the terms and conditions of version 3 of the GNU General Public
|
||||||
|
License, supplemented by the additional permissions listed below.
|
||||||
|
|
||||||
|
0. Additional Definitions.
|
||||||
|
|
||||||
|
As used herein, "this License" refers to version 3 of the GNU Lesser
|
||||||
|
General Public License, and the "GNU GPL" refers to version 3 of the GNU
|
||||||
|
General Public License.
|
||||||
|
|
||||||
|
"The Library" refers to a covered work governed by this License,
|
||||||
|
other than an Application or a Combined Work as defined below.
|
||||||
|
|
||||||
|
An "Application" is any work that makes use of an interface provided
|
||||||
|
by the Library, but which is not otherwise based on the Library.
|
||||||
|
Defining a subclass of a class defined by the Library is deemed a mode
|
||||||
|
of using an interface provided by the Library.
|
||||||
|
|
||||||
|
A "Combined Work" is a work produced by combining or linking an
|
||||||
|
Application with the Library. The particular version of the Library
|
||||||
|
with which the Combined Work was made is also called the "Linked
|
||||||
|
Version".
|
||||||
|
|
||||||
|
The "Minimal Corresponding Source" for a Combined Work means the
|
||||||
|
Corresponding Source for the Combined Work, excluding any source code
|
||||||
|
for portions of the Combined Work that, considered in isolation, are
|
||||||
|
based on the Application, and not on the Linked Version.
|
||||||
|
|
||||||
|
The "Corresponding Application Code" for a Combined Work means the
|
||||||
|
object code and/or source code for the Application, including any data
|
||||||
|
and utility programs needed for reproducing the Combined Work from the
|
||||||
|
Application, but excluding the System Libraries of the Combined Work.
|
||||||
|
|
||||||
|
1. Exception to Section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
You may convey a covered work under sections 3 and 4 of this License
|
||||||
|
without being bound by section 3 of the GNU GPL.
|
||||||
|
|
||||||
|
2. Conveying Modified Versions.
|
||||||
|
|
||||||
|
If you modify a copy of the Library, and, in your modifications, a
|
||||||
|
facility refers to a function or data to be supplied by an Application
|
||||||
|
that uses the facility (other than as an argument passed when the
|
||||||
|
facility is invoked), then you may convey a copy of the modified
|
||||||
|
version:
|
||||||
|
|
||||||
|
a) under this License, provided that you make a good faith effort to
|
||||||
|
ensure that, in the event an Application does not supply the
|
||||||
|
function or data, the facility still operates, and performs
|
||||||
|
whatever part of its purpose remains meaningful, or
|
||||||
|
|
||||||
|
b) under the GNU GPL, with none of the additional permissions of
|
||||||
|
this License applicable to that copy.
|
||||||
|
|
||||||
|
3. Object Code Incorporating Material from Library Header Files.
|
||||||
|
|
||||||
|
The object code form of an Application may incorporate material from
|
||||||
|
a header file that is part of the Library. You may convey such object
|
||||||
|
code under terms of your choice, provided that, if the incorporated
|
||||||
|
material is not limited to numerical parameters, data structure
|
||||||
|
layouts and accessors, or small macros, inline functions and templates
|
||||||
|
(ten or fewer lines in length), you do both of the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the object code that the
|
||||||
|
Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the object code with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
4. Combined Works.
|
||||||
|
|
||||||
|
You may convey a Combined Work under terms of your choice that,
|
||||||
|
taken together, effectively do not restrict modification of the
|
||||||
|
portions of the Library contained in the Combined Work and reverse
|
||||||
|
engineering for debugging such modifications, if you also do each of
|
||||||
|
the following:
|
||||||
|
|
||||||
|
a) Give prominent notice with each copy of the Combined Work that
|
||||||
|
the Library is used in it and that the Library and its use are
|
||||||
|
covered by this License.
|
||||||
|
|
||||||
|
b) Accompany the Combined Work with a copy of the GNU GPL and this license
|
||||||
|
document.
|
||||||
|
|
||||||
|
c) For a Combined Work that displays copyright notices during
|
||||||
|
execution, include the copyright notice for the Library among
|
||||||
|
these notices, as well as a reference directing the user to the
|
||||||
|
copies of the GNU GPL and this license document.
|
||||||
|
|
||||||
|
d) Do one of the following:
|
||||||
|
|
||||||
|
0) Convey the Minimal Corresponding Source under the terms of this
|
||||||
|
License, and the Corresponding Application Code in a form
|
||||||
|
suitable for, and under terms that permit, the user to
|
||||||
|
recombine or relink the Application with a modified version of
|
||||||
|
the Linked Version to produce a modified Combined Work, in the
|
||||||
|
manner specified by section 6 of the GNU GPL for conveying
|
||||||
|
Corresponding Source.
|
||||||
|
|
||||||
|
1) Use a suitable shared library mechanism for linking with the
|
||||||
|
Library. A suitable mechanism is one that (a) uses at run time
|
||||||
|
a copy of the Library already present on the user's computer
|
||||||
|
system, and (b) will operate properly with a modified version
|
||||||
|
of the Library that is interface-compatible with the Linked
|
||||||
|
Version.
|
||||||
|
|
||||||
|
e) Provide Installation Information, but only if you would otherwise
|
||||||
|
be required to provide such information under section 6 of the
|
||||||
|
GNU GPL, and only to the extent that such information is
|
||||||
|
necessary to install and execute a modified version of the
|
||||||
|
Combined Work produced by recombining or relinking the
|
||||||
|
Application with a modified version of the Linked Version. (If
|
||||||
|
you use option 4d0, the Installation Information must accompany
|
||||||
|
the Minimal Corresponding Source and Corresponding Application
|
||||||
|
Code. If you use option 4d1, you must provide the Installation
|
||||||
|
Information in the manner specified by section 6 of the GNU GPL
|
||||||
|
for conveying Corresponding Source.)
|
||||||
|
|
||||||
|
5. Combined Libraries.
|
||||||
|
|
||||||
|
You may place library facilities that are a work based on the
|
||||||
|
Library side by side in a single library together with other library
|
||||||
|
facilities that are not Applications and are not covered by this
|
||||||
|
License, and convey such a combined library under terms of your
|
||||||
|
choice, if you do both of the following:
|
||||||
|
|
||||||
|
a) Accompany the combined library with a copy of the same work based
|
||||||
|
on the Library, uncombined with any other library facilities,
|
||||||
|
conveyed under the terms of this License.
|
||||||
|
|
||||||
|
b) Give prominent notice with the combined library that part of it
|
||||||
|
is a work based on the Library, and explaining where to find the
|
||||||
|
accompanying uncombined form of the same work.
|
||||||
|
|
||||||
|
6. Revised Versions of the GNU Lesser General Public License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions
|
||||||
|
of the GNU Lesser General Public License from time to time. Such new
|
||||||
|
versions will be similar in spirit to the present version, but may
|
||||||
|
differ in detail to address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Library as you received it specifies that a certain numbered version
|
||||||
|
of the GNU Lesser General Public License "or any later version"
|
||||||
|
applies to it, you have the option of following the terms and
|
||||||
|
conditions either of that published version or of any later version
|
||||||
|
published by the Free Software Foundation. If the Library as you
|
||||||
|
received it does not specify a version number of the GNU Lesser
|
||||||
|
General Public License, you may choose any version of the GNU Lesser
|
||||||
|
General Public License ever published by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Library as you received it specifies that a proxy can decide
|
||||||
|
whether future versions of the GNU Lesser General Public License shall
|
||||||
|
apply, that proxy's public statement of acceptance of any version is
|
||||||
|
permanent authorization for you to choose that version for the
|
||||||
|
Library.
|
||||||
48
esbuild.config.mjs
Normal file
48
esbuild.config.mjs
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
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[3] === "production";
|
||||||
|
|
||||||
|
const context = await esbuild.context({
|
||||||
|
banner: {
|
||||||
|
js: banner,
|
||||||
|
},
|
||||||
|
entryPoints: ["src/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: "dist/main.js",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (prod) {
|
||||||
|
await context.rebuild();
|
||||||
|
process.exit(0);
|
||||||
|
} else {
|
||||||
|
await context.watch();
|
||||||
|
}
|
||||||
22
eslint.config.mjs
Normal file
22
eslint.config.mjs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
import eslint from "@eslint/js";
|
||||||
|
import tseslint from "typescript-eslint";
|
||||||
|
|
||||||
|
export default tseslint.config(
|
||||||
|
eslint.configs.recommended,
|
||||||
|
tseslint.configs.strictTypeChecked,
|
||||||
|
tseslint.configs.stylisticTypeChecked,
|
||||||
|
{
|
||||||
|
rules: {
|
||||||
|
"@typescript-eslint/restrict-template-expressions": [
|
||||||
|
"error",
|
||||||
|
{ allowNumber: true },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
languageOptions: {
|
||||||
|
parserOptions: {
|
||||||
|
projectService: true,
|
||||||
|
tsconfigRootDir: import.meta.dirname,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
27
flake.lock
Normal file
27
flake.lock
Normal file
|
|
@ -0,0 +1,27 @@
|
||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1713564160,
|
||||||
|
"narHash": "sha256-YguPZpiejgzLEcO36/SZULjJQ55iWcjAmf3lYiyV1Fo=",
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "bc194f70731cc5d2b046a6c1b3b15f170f05999c",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nixos",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "bc194f70731cc5d2b046a6c1b3b15f170f05999c",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
31
flake.nix
Normal file
31
flake.nix
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
description = "A Nix-flake-based Node.js development environment";
|
||||||
|
|
||||||
|
inputs = {
|
||||||
|
nixpkgs.url = "github:nixos/nixpkgs?rev=bc194f70731cc5d2b046a6c1b3b15f170f05999c";
|
||||||
|
};
|
||||||
|
|
||||||
|
outputs = { nixpkgs, ... }:
|
||||||
|
let
|
||||||
|
system = "x86_64-linux";
|
||||||
|
in
|
||||||
|
{
|
||||||
|
devShells."${system}".default =
|
||||||
|
let
|
||||||
|
pkgs = import nixpkgs {
|
||||||
|
inherit system;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
pkgs.mkShell {
|
||||||
|
# create an environment with nodejs_18, pnpm, and yarn
|
||||||
|
packages = with pkgs; [
|
||||||
|
nodejs_18
|
||||||
|
nodePackages.pnpm
|
||||||
|
];
|
||||||
|
|
||||||
|
shellHook = ''
|
||||||
|
echo "node `${pkgs.nodejs}/bin/node --version`"
|
||||||
|
'';
|
||||||
|
};
|
||||||
|
};
|
||||||
|
}
|
||||||
1
obsidian-reusables
Submodule
1
obsidian-reusables
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit e7b9a59117755b51386c59cf68e20d6d1bbe1d3c
|
||||||
1
obsidian-typings
Submodule
1
obsidian-typings
Submodule
|
|
@ -0,0 +1 @@
|
||||||
|
Subproject commit 525118c91be1bdb129c211acc08578a1d5a666c3
|
||||||
36
package.json
Normal file
36
package.json
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"name": "crosslink-advanced",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"main": "./src/main.ts",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "node esbuild.config.mjs",
|
||||||
|
"build": "node esbuild.config.mjs production",
|
||||||
|
"typecheck": "tsc -noEmit -skipLibCheck",
|
||||||
|
"version": "node version-bump.mjs && git add manifest.json versions.json",
|
||||||
|
"lint": "eslint src/",
|
||||||
|
"lint:fix": "eslint --fix src/",
|
||||||
|
"format": "prettier --write .",
|
||||||
|
"format:check": "prettier --check ."
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "LGPL-3.0-only",
|
||||||
|
"devDependencies": {
|
||||||
|
"@eslint/js": "^9.20.0",
|
||||||
|
"@tsconfig/strictest": "^2.0.5",
|
||||||
|
"@types/lodash-es": "^4.17.12",
|
||||||
|
"@types/node": "^16.11.6",
|
||||||
|
"builtin-modules": "3.3.0",
|
||||||
|
"esbuild": "0.17.3",
|
||||||
|
"eslint": "^9.20.1",
|
||||||
|
"obsidian": "1.7.2",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"typescript": "^5.7.3",
|
||||||
|
"typescript-eslint": "^8.24.1"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"lodash-es": "^4.17.21",
|
||||||
|
"monkey-around": "^3.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
1224
pnpm-lock.yaml
Normal file
1224
pnpm-lock.yaml
Normal file
File diff suppressed because it is too large
Load diff
541
src/main.ts
Normal file
541
src/main.ts
Normal file
|
|
@ -0,0 +1,541 @@
|
||||||
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||||
|
/* eslint-disable @typescript-eslint/no-deprecated */
|
||||||
|
import {
|
||||||
|
App,
|
||||||
|
FileView,
|
||||||
|
FuzzyMatch,
|
||||||
|
FuzzySuggestModal,
|
||||||
|
Notice,
|
||||||
|
TFile,
|
||||||
|
TFolder,
|
||||||
|
View,
|
||||||
|
setIcon,
|
||||||
|
} from "obsidian";
|
||||||
|
|
||||||
|
import {
|
||||||
|
forceFile,
|
||||||
|
isIndexFile,
|
||||||
|
forceFolder,
|
||||||
|
movableFile,
|
||||||
|
folderPrefix,
|
||||||
|
} from "../obsidian-reusables/src/indexFiles";
|
||||||
|
import {
|
||||||
|
getFileParentIndexes,
|
||||||
|
getFileIsTargetFileTagFilter,
|
||||||
|
} from "../obsidian-reusables/src/ftags";
|
||||||
|
import PluginWithSettings from "../obsidian-reusables/src/PluginWithSettings";
|
||||||
|
import { DEFAULT_SETTINGS, MainPluginSettingsTab } from "./settings";
|
||||||
|
|
||||||
|
const allFilesFilter = (file: TFile): boolean =>
|
||||||
|
file.extension === "md" || file.extension === "dir";
|
||||||
|
|
||||||
|
export default class Main extends PluginWithSettings(DEFAULT_SETTINGS) {
|
||||||
|
override async onload() {
|
||||||
|
await this.initSettings(MainPluginSettingsTab);
|
||||||
|
this.addCommand({
|
||||||
|
id: "add-ftag",
|
||||||
|
name: "Add ftag to the note",
|
||||||
|
icon: "folder-symlink",
|
||||||
|
callback: this.openAddFtagModal.bind(this),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: "open-random-inbox-item",
|
||||||
|
name: "Open random inbox item",
|
||||||
|
icon: "dices",
|
||||||
|
callback: this.openRandomInboxItem.bind(this),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: "rename-and-keep-as-alias",
|
||||||
|
name: "Rename swapping for alias",
|
||||||
|
icon: "edit",
|
||||||
|
callback: this.renameAndKeepAsAlias.bind(this),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: "remove-inbox-tag",
|
||||||
|
name: "Remove inbox tag",
|
||||||
|
icon: "combine",
|
||||||
|
callback: this.removeInboxTag.bind(this),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.addCommand({
|
||||||
|
id: "create-note-as-child",
|
||||||
|
name: "Create new note as child",
|
||||||
|
icon: "corner-down-right",
|
||||||
|
callback: this.createNoteAsChild.bind(this),
|
||||||
|
});
|
||||||
|
|
||||||
|
this.registerEvent(
|
||||||
|
this.app.workspace.on("file-menu", (menu, file) => {
|
||||||
|
if (file instanceof TFile && file.extension === "md") {
|
||||||
|
menu.addItem((item) => {
|
||||||
|
item.setTitle("Rename swapping for alias")
|
||||||
|
.setIcon("edit")
|
||||||
|
.onClick(async () => {
|
||||||
|
await this.renameAndKeepAsAlias(file);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
this.addRibbonIcon(
|
||||||
|
"dices",
|
||||||
|
"Open random inbox item",
|
||||||
|
this.openRandomInboxItem.bind(this),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async renameAndKeepAsAlias(file = this.getCurrentFile()) {
|
||||||
|
if (!file || file.extension !== "md") return;
|
||||||
|
|
||||||
|
const currentName = file.getShortName();
|
||||||
|
|
||||||
|
await this.app.fileManager.promptForFileRename(file);
|
||||||
|
|
||||||
|
function isObj(v: unknown) {
|
||||||
|
return typeof v === "object" && !!v;
|
||||||
|
}
|
||||||
|
function hasAliases(v: unknown): v is { aliases?: string[] } {
|
||||||
|
return (
|
||||||
|
isObj(v) &&
|
||||||
|
(("aliases" in v && Array.isArray(v.aliases)) ||
|
||||||
|
!("aliases" in v))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
await this.app.fileManager.processFrontMatter(file, (v) => {
|
||||||
|
if (!hasAliases(v)) return;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||||
|
v.aliases ||= [];
|
||||||
|
v.aliases.push(currentName);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public async removeInboxTag() {
|
||||||
|
const indexFile = this.app.workspace.getActiveFile();
|
||||||
|
const currentFile = movableFile(indexFile);
|
||||||
|
if (!currentFile || !indexFile) return;
|
||||||
|
|
||||||
|
if (currentFile.parent?.path !== this.settings.inbox) return;
|
||||||
|
|
||||||
|
const tags = this.app.vault
|
||||||
|
.getFiles()
|
||||||
|
.filter(getFileIsTargetFileTagFilter(currentFile, this.app));
|
||||||
|
|
||||||
|
const nonInboxTag = tags.find(
|
||||||
|
(v) => movableFile(v).path !== this.settings.inbox,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (nonInboxTag) {
|
||||||
|
await this.removeFileSymlinkTo(indexFile, nonInboxTag);
|
||||||
|
const { folder } = await forceFolder(nonInboxTag, this.app);
|
||||||
|
await this.app.fileManager.renameFile(
|
||||||
|
currentFile,
|
||||||
|
folderPrefix(folder) + currentFile.name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public openRandomInboxItem() {
|
||||||
|
const res = this.app.vault.getAbstractFileByPath(this.settings.inbox);
|
||||||
|
const currentFile = this.app.workspace.getActiveFile();
|
||||||
|
if (res instanceof TFolder) {
|
||||||
|
const children = res.children.flatMap((child) => {
|
||||||
|
if (child instanceof TFile && child.extension !== "dir")
|
||||||
|
return child;
|
||||||
|
if (child instanceof TFolder)
|
||||||
|
return child.children.filter(isIndexFile).slice(0, 1);
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
const chosen = children
|
||||||
|
.filter((v) => v !== currentFile)
|
||||||
|
.shuffle()[0];
|
||||||
|
if (chosen) {
|
||||||
|
void this.app.workspace.openLinkText(chosen.path, "/");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private getCurrentFile() {
|
||||||
|
const view = this.app.workspace.activeLeaf?.view;
|
||||||
|
const isFileView = (v: View): v is FileView => "file" in v;
|
||||||
|
if (!view || !isFileView(view)) return null;
|
||||||
|
|
||||||
|
return view.file;
|
||||||
|
}
|
||||||
|
public openAddFtagModal() {
|
||||||
|
const editedFile = this.getCurrentFile();
|
||||||
|
if (!editedFile) return;
|
||||||
|
const getDefaultPlaceholder = () =>
|
||||||
|
`Choose tags from all files for "${editedFile.basename}"`;
|
||||||
|
|
||||||
|
const parents = new Set(getFileParentIndexes(editedFile, this.app));
|
||||||
|
const allFilesExceptParents = (v: TFile) =>
|
||||||
|
allFilesFilter(v) && !parents.has(v) && v !== editedFile;
|
||||||
|
const modal = new FileAndDirChooser(this.app, {
|
||||||
|
filter: allFilesExceptParents,
|
||||||
|
inbox: this.settings.inbox,
|
||||||
|
onSelect: (file) => void this.addFtag(editedFile, file),
|
||||||
|
customFileActions: [
|
||||||
|
{
|
||||||
|
icon: "tags",
|
||||||
|
text: "Show this file's tags",
|
||||||
|
cb: (_, selectedFile) => {
|
||||||
|
modal.setPlaceholder(
|
||||||
|
`Choose tags from "${selectedFile.basename}" for "${editedFile.basename}"`,
|
||||||
|
);
|
||||||
|
modal.setFilter(
|
||||||
|
getFileIsTargetFileTagFilter(
|
||||||
|
selectedFile,
|
||||||
|
this.app,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
modal.clearQuery();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
customActions: [
|
||||||
|
{
|
||||||
|
icon: "rotate-ccw",
|
||||||
|
text: "Reset filters and target file",
|
||||||
|
cb: () => {
|
||||||
|
modal.setPlaceholder(getDefaultPlaceholder());
|
||||||
|
modal.setFilter(allFilesExceptParents);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
modal.setPlaceholder(getDefaultPlaceholder());
|
||||||
|
modal.open();
|
||||||
|
}
|
||||||
|
|
||||||
|
private addFtag = async (file: TFile, ftag: TFile) => {
|
||||||
|
if (movableFile(file).parent?.path === this.settings.inbox) {
|
||||||
|
const movedFile = isIndexFile(file) ? file.parent! : file;
|
||||||
|
const { folder } = await forceFolder(ftag, this.app);
|
||||||
|
|
||||||
|
await this.app.fileManager.renameFile(
|
||||||
|
movedFile,
|
||||||
|
`${folder.path}/${movedFile.name}`,
|
||||||
|
);
|
||||||
|
|
||||||
|
new Notice(`Added ftag ${ftag.basename} by moving`);
|
||||||
|
} else {
|
||||||
|
const { index } = await forceFolder(ftag, this.app);
|
||||||
|
const forcedIndex = await forceFile(index, this.app);
|
||||||
|
|
||||||
|
await this.addFileSymlinkTo(file, forcedIndex);
|
||||||
|
new Notice(`Added ftag ${ftag.basename} via link`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public async createNoteAsChild() {
|
||||||
|
const currentFile = this.app.workspace.getActiveFile();
|
||||||
|
if (!currentFile) return;
|
||||||
|
const { folder } = await forceFolder(currentFile, this.app);
|
||||||
|
const note = await this.app.fileManager.createNewMarkdownFile(
|
||||||
|
folder,
|
||||||
|
"Untitled",
|
||||||
|
"",
|
||||||
|
);
|
||||||
|
await this.app.workspace.openLinkText(note.path, "/", "tab", {
|
||||||
|
active: true,
|
||||||
|
eState: { rename: "all" },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async removeFileSymlinkTo(file: TFile, forcedIndex: TFile) {
|
||||||
|
await this.app.fileManager.processFrontMatter(
|
||||||
|
forcedIndex,
|
||||||
|
(frontmatter: { symlinks?: unknown }) => {
|
||||||
|
if (Array.isArray(frontmatter.symlinks)) {
|
||||||
|
frontmatter.symlinks = frontmatter.symlinks.filter(
|
||||||
|
(v: string) =>
|
||||||
|
this.app.metadataCache.getFirstLinkpathDest(
|
||||||
|
v.slice(2, -2),
|
||||||
|
forcedIndex.path,
|
||||||
|
) !== file,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
private async addFileSymlinkTo(file: TFile, forcedIndex: TFile) {
|
||||||
|
const fileLink = this.app.fileManager
|
||||||
|
.generateMarkdownLink(file, file.path)
|
||||||
|
.replace(/^!/, "");
|
||||||
|
await this.app.fileManager.processFrontMatter(
|
||||||
|
forcedIndex,
|
||||||
|
(frontmatter: { symlinks?: unknown }) => {
|
||||||
|
if (!frontmatter.symlinks) {
|
||||||
|
frontmatter.symlinks = [fileLink];
|
||||||
|
} else if (Array.isArray(frontmatter.symlinks)) {
|
||||||
|
frontmatter.symlinks = [
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
...frontmatter.symlinks.filter((v) => v !== fileLink),
|
||||||
|
fileLink,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Options {
|
||||||
|
filter: (file: TFile) => boolean;
|
||||||
|
onSelect: (file: TFile) => void;
|
||||||
|
inbox: string;
|
||||||
|
customFileActions?: {
|
||||||
|
icon: string;
|
||||||
|
text: string;
|
||||||
|
cb: (e: MouseEvent, file: TFile) => void;
|
||||||
|
}[];
|
||||||
|
customActions?: {
|
||||||
|
icon: string;
|
||||||
|
text: string;
|
||||||
|
cb: (e: MouseEvent, query: string) => void;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
export class FileAndDirChooser extends FuzzySuggestModal<TFile> {
|
||||||
|
public setFilter(filter: Options["filter"]) {
|
||||||
|
this.filter = filter;
|
||||||
|
this.recalculateSuggestions();
|
||||||
|
}
|
||||||
|
private inbox: string;
|
||||||
|
constructor(app: App, opts: Options) {
|
||||||
|
super(app);
|
||||||
|
this.inbox = opts.inbox;
|
||||||
|
this.filter = opts.filter;
|
||||||
|
this.onSelect = opts.onSelect;
|
||||||
|
this.customFileActions = opts.customFileActions ?? [];
|
||||||
|
this.customActions = opts.customActions ?? [];
|
||||||
|
|
||||||
|
this.setInstructions([
|
||||||
|
{ command: "↑↓", purpose: "to navigate" },
|
||||||
|
{
|
||||||
|
command: "shift ↵",
|
||||||
|
purpose: "to create new note and use as new tag",
|
||||||
|
},
|
||||||
|
{ command: "ctrl ↵", purpose: "to use selected file as new tag" },
|
||||||
|
{
|
||||||
|
command: "alt ↵",
|
||||||
|
purpose: "to use selected file as new tag and clear",
|
||||||
|
},
|
||||||
|
{ command: "↵", purpose: "to use file as new tag and close" },
|
||||||
|
{ command: "esc", purpose: "to dismiss" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-misused-promises
|
||||||
|
this.inputEl.addEventListener("keydown", async (evt) => {
|
||||||
|
if (evt.key === "Enter") {
|
||||||
|
if (
|
||||||
|
(evt.shiftKey || this.isWithoutShownSuggestions) &&
|
||||||
|
!this.isEmptyQuery
|
||||||
|
) {
|
||||||
|
await this.createNoteFromQuery({
|
||||||
|
close: !evt.ctrlKey,
|
||||||
|
clear: evt.altKey,
|
||||||
|
});
|
||||||
|
} else if (evt.ctrlKey || evt.altKey) {
|
||||||
|
this.selectHighlightedFile({ clear: evt.altKey });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cta =
|
||||||
|
this.inputEl.parentElement!.querySelector(".prompt-input-cta");
|
||||||
|
|
||||||
|
const addButton = (
|
||||||
|
icon: string,
|
||||||
|
text: string,
|
||||||
|
cb: (e: MouseEvent) => void,
|
||||||
|
phoneOnly?: boolean,
|
||||||
|
) => {
|
||||||
|
const button = createEl(
|
||||||
|
"button",
|
||||||
|
"clickable-icon " + (phoneOnly ? "only-phone" : ""),
|
||||||
|
(e) => {
|
||||||
|
setIcon(e, icon);
|
||||||
|
e.setAttribute("aria-label", text);
|
||||||
|
e.onclick = (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
cb(e);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
cta?.appendChild(button);
|
||||||
|
};
|
||||||
|
|
||||||
|
addButton("file-plus", "Create note with given name", (e) => {
|
||||||
|
void this.createNoteFromQuery({
|
||||||
|
close: !e.ctrlKey && !e.altKey,
|
||||||
|
clear: e.altKey,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addButton(
|
||||||
|
"file-stack",
|
||||||
|
"Create note and clear input",
|
||||||
|
() => {
|
||||||
|
void this.createNoteFromQuery({
|
||||||
|
close: false,
|
||||||
|
clear: true,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const conf of this.customActions) {
|
||||||
|
addButton(conf.icon, conf.text, (e) => {
|
||||||
|
conf.cb(e, this.inputEl.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private onSelect: Options["onSelect"];
|
||||||
|
private filter: Options["filter"];
|
||||||
|
private customFileActions: NonNullable<Options["customFileActions"]>;
|
||||||
|
private customActions: NonNullable<Options["customActions"]>;
|
||||||
|
|
||||||
|
private async createNoteFromQuery({
|
||||||
|
close,
|
||||||
|
clear,
|
||||||
|
}: {
|
||||||
|
close?: boolean;
|
||||||
|
clear?: boolean;
|
||||||
|
}) {
|
||||||
|
const newNote =
|
||||||
|
await this.app.fileManager.createNewMarkdownFileFromLinktext(
|
||||||
|
this.currentQuery,
|
||||||
|
this.inbox,
|
||||||
|
);
|
||||||
|
this.onSelect(newNote);
|
||||||
|
if (close) {
|
||||||
|
this.close();
|
||||||
|
}
|
||||||
|
if (clear) {
|
||||||
|
this.clearQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private selectHighlightedFile({ clear }: { clear: boolean }) {
|
||||||
|
const file = this.highlightedFile;
|
||||||
|
if (file) {
|
||||||
|
this.onSelect(file);
|
||||||
|
if (clear) {
|
||||||
|
this.clearQuery();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private get highlightedFile() {
|
||||||
|
if (
|
||||||
|
this.hiddenChooser.selectedItem != null &&
|
||||||
|
this.hiddenChooser.values
|
||||||
|
) {
|
||||||
|
return this.hiddenChooser.values[this.hiddenChooser.selectedItem]!
|
||||||
|
.item;
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
private get isEmptyQuery() {
|
||||||
|
return this.inputEl.value.trim().length === 0;
|
||||||
|
}
|
||||||
|
private get isWithoutShownSuggestions() {
|
||||||
|
return this.hiddenChooser.suggestions.length === 0;
|
||||||
|
}
|
||||||
|
private get currentQuery() {
|
||||||
|
return this.inputEl.value;
|
||||||
|
}
|
||||||
|
private get hiddenChooser() {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
|
||||||
|
const chooser = (this as any).chooser as {
|
||||||
|
selectedItem: number | null;
|
||||||
|
values: { item: TFile }[] | null;
|
||||||
|
suggestions: unknown[];
|
||||||
|
};
|
||||||
|
return chooser;
|
||||||
|
}
|
||||||
|
|
||||||
|
public clearQuery() {
|
||||||
|
this.inputEl.value = "";
|
||||||
|
this.recalculateSuggestions();
|
||||||
|
}
|
||||||
|
private recalculateSuggestions() {
|
||||||
|
this.inputEl.dispatchEvent(new InputEvent("input"));
|
||||||
|
}
|
||||||
|
|
||||||
|
getItems(): TFile[] {
|
||||||
|
const lastOpenedFiles =
|
||||||
|
this.app.workspace.recentFileTracker.lastOpenFiles;
|
||||||
|
const set = new Set(lastOpenedFiles);
|
||||||
|
const files = this.app.vault.getFiles();
|
||||||
|
return files.filter(this.filter).sort((b, a) => {
|
||||||
|
if (set.has(a.path) && !set.has(b.path)) return 1;
|
||||||
|
if (!set.has(a.path) && set.has(b.path)) return -1;
|
||||||
|
if (!set.has(a.path) && !set.has(b.path))
|
||||||
|
return a.stat.mtime - b.stat.mtime;
|
||||||
|
return -(
|
||||||
|
lastOpenedFiles.indexOf(a.path) -
|
||||||
|
lastOpenedFiles.indexOf(b.path)
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
override renderSuggestion(item: FuzzyMatch<TFile>, el: HTMLElement): void {
|
||||||
|
(FuzzySuggestModal<TFile>).prototype.renderSuggestion.apply(this, [
|
||||||
|
item,
|
||||||
|
el,
|
||||||
|
]);
|
||||||
|
const content = el.createDiv("suggestion-content");
|
||||||
|
const title = content.createDiv("suggestion-title");
|
||||||
|
for (const child of Array.from(el.childNodes)) {
|
||||||
|
if (child === content) continue;
|
||||||
|
title.appendChild(child);
|
||||||
|
}
|
||||||
|
el.classList.add("mod-complex");
|
||||||
|
const aux = el.createDiv("suggestion-aux");
|
||||||
|
const createAuxButton = (
|
||||||
|
icon: string,
|
||||||
|
text: string,
|
||||||
|
cb: (e: MouseEvent) => void,
|
||||||
|
) => {
|
||||||
|
const flair = aux.createSpan("suggestion-flair");
|
||||||
|
flair.setAttribute("aria-label", text);
|
||||||
|
flair.onclick = (evt) => {
|
||||||
|
evt.stopPropagation();
|
||||||
|
evt.preventDefault();
|
||||||
|
cb(evt);
|
||||||
|
};
|
||||||
|
setIcon(flair, icon);
|
||||||
|
};
|
||||||
|
createAuxButton(
|
||||||
|
"mouse-pointer-click",
|
||||||
|
"Choose note without closing modal",
|
||||||
|
() => {
|
||||||
|
this.selectHighlightedFile({ clear: false });
|
||||||
|
},
|
||||||
|
);
|
||||||
|
createAuxButton("search-slash", "Choose note and clear query", () => {
|
||||||
|
this.selectHighlightedFile({ clear: true });
|
||||||
|
});
|
||||||
|
for (const opts of this.customFileActions) {
|
||||||
|
createAuxButton(opts.icon, opts.text, (e) => {
|
||||||
|
opts.cb(e, item.item);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getItemText(file: TFile): string {
|
||||||
|
const isIndex = file.basename === file.parent?.name;
|
||||||
|
if (isIndex) return file.parent?.path ?? file.path;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
||||||
|
const aliases = this.app.metadataCache.getCache(file.path)
|
||||||
|
?.frontmatter?.["aliases"];
|
||||||
|
if (Array.isArray(aliases))
|
||||||
|
return file.path + "\n" + aliases.join(", ");
|
||||||
|
return file.path;
|
||||||
|
}
|
||||||
|
|
||||||
|
onChooseItem(file: TFile) {
|
||||||
|
this.onSelect(file);
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/manifest.json
Normal file
11
src/manifest.json
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"id": "crosslink-advanced",
|
||||||
|
"name": "Crosslink Advanced",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"minAppVersion": "0.15.0",
|
||||||
|
"description": "Allows to tag files using folders and symlinks system.",
|
||||||
|
"author": "d7sd6u",
|
||||||
|
"authorUrl": "https://github.com/d7sd6u",
|
||||||
|
"fundingUrl": "https://ko-fi.com/d7sd6u",
|
||||||
|
"isDesktopOnly": false
|
||||||
|
}
|
||||||
36
src/settings.ts
Normal file
36
src/settings.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
import { App, PluginSettingTab, Setting } from "obsidian";
|
||||||
|
import Main from "./main";
|
||||||
|
|
||||||
|
export const DEFAULT_SETTINGS = {
|
||||||
|
inbox: "Uncategorized",
|
||||||
|
};
|
||||||
|
export class MainPluginSettingsTab extends PluginSettingTab {
|
||||||
|
constructor(
|
||||||
|
app: App,
|
||||||
|
override plugin: Main,
|
||||||
|
) {
|
||||||
|
super(app, plugin);
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
display() {
|
||||||
|
const { containerEl } = this;
|
||||||
|
containerEl.empty();
|
||||||
|
const options = Object.fromEntries(
|
||||||
|
this.app.vault.getAllFolders().map((v) => [v.path, v.path]),
|
||||||
|
);
|
||||||
|
options["/"] = "/";
|
||||||
|
new Setting(containerEl)
|
||||||
|
.setName("Inbox folder")
|
||||||
|
.setDesc("Folder where notes without explicit ftags are stored")
|
||||||
|
.addDropdown((textArea) => {
|
||||||
|
textArea
|
||||||
|
.addOptions(options)
|
||||||
|
.setValue(this.plugin.settings.inbox)
|
||||||
|
.onChange(async (v) => {
|
||||||
|
this.plugin.settings.inbox = v;
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/styles.css
Normal file
20
src/styles.css
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
/*
|
||||||
|
|
||||||
|
This CSS file will be included with your plugin, and
|
||||||
|
available in the app when your plugin is enabled.
|
||||||
|
|
||||||
|
If your plugin does not need CSS, delete this file.
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
.prompt-input-cta {
|
||||||
|
padding: var(--size-4-3);
|
||||||
|
gap: var(--size-4-1);
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
.only-phone {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.is-phone .only-phone {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
3
src/versions.json
Normal file
3
src/versions.json
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"1.0.0": "0.15.0"
|
||||||
|
}
|
||||||
18
tsconfig.json
Normal file
18
tsconfig.json
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"extends": "@tsconfig/strictest/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": ".",
|
||||||
|
"inlineSourceMap": true,
|
||||||
|
"inlineSources": true,
|
||||||
|
"module": "ESNext",
|
||||||
|
"target": "ES6",
|
||||||
|
"allowJs": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"importHelpers": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"lib": ["DOM", "ES5", "ES6", "ES7"]
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts", "obsidian-typings/src/**/*.d.ts"]
|
||||||
|
}
|
||||||
14
version-bump.mjs
Normal file
14
version-bump.mjs
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
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"));
|
||||||
Loading…
Reference in a new issue