v0.1.0
|
|
@ -1,10 +0,0 @@
|
|||
# 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,3 +0,0 @@
|
|||
node_modules/
|
||||
|
||||
main.js
|
||||
23
.eslintrc
|
|
@ -1,23 +0,0 @@
|
|||
{
|
||||
"root": true,
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"env": { "node": true },
|
||||
"plugins": [
|
||||
"@typescript-eslint"
|
||||
],
|
||||
"extends": [
|
||||
"eslint:recommended",
|
||||
"plugin:@typescript-eslint/eslint-recommended",
|
||||
"plugin:@typescript-eslint/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"no-unused-vars": "off",
|
||||
"@typescript-eslint/no-unused-vars": ["error", { "args": "none" }],
|
||||
"@typescript-eslint/ban-ts-comment": "off",
|
||||
"no-prototype-builtins": "off",
|
||||
"@typescript-eslint/no-empty-function": "off"
|
||||
}
|
||||
}
|
||||
17
.gitignore
vendored
|
|
@ -1,22 +1,5 @@
|
|||
# vscode
|
||||
.vscode
|
||||
|
||||
# Intellij
|
||||
*.iml
|
||||
.idea
|
||||
|
||||
# npm
|
||||
node_modules
|
||||
|
||||
# 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
|
||||
|
|
|
|||
1
.npmrc
|
|
@ -1 +0,0 @@
|
|||
tag-version-prefix=""
|
||||
21
LICENSE
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
364
README.md
|
|
@ -1,96 +1,318 @@
|
|||
# Obsidian Sample Plugin
|
||||
# HK CodeBlock
|
||||
|
||||
This is a sample plugin for Obsidian (https://obsidian.md).
|
||||
[Obsidian](https://obsidian.md) plugin developed by Heekang Park
|
||||
|
||||
This project uses Typescript to provide type checking and documentation.
|
||||
The repo depends on the latest plugin API (obsidian.d.ts) in Typescript Definition format, which contains TSDoc comments describing what it does.
|
||||
[ English | [한국어](https://github.com/HeekangPark/obsidian-hk-code-block/blob/master/README_ko.md) ]
|
||||
|
||||
**Note:** The Obsidian API is still in early alpha and is subject to change at any time!
|
||||
With HK CodeBlock, you can add various features to code blocks.
|
||||
|
||||
This sample plugin demonstrates some of the basic functionality the plugin API can do.
|
||||
- Changes the default font color to red using `styles.css`.
|
||||
- Adds a ribbon icon, which shows a Notice when clicked.
|
||||
- Adds a command "Open Sample Modal" which opens a Modal.
|
||||
- Adds a plugin setting tab to the settings page.
|
||||
- Registers a global click event and output 'click' to the console.
|
||||
- Registers a global interval which logs 'setInterval' to the console.
|
||||
Inspired from [Prism.js](https://prismjs.com/)'s plugin, [Better Code Block](https://github.com/stargrey/obsidian-better-codeblock), [Code Block Enhancer](https://github.com/nyable/obsidian-code-block-enhancer).
|
||||
|
||||
## First time developing plugins?
|
||||
## Features
|
||||
|
||||
Quick starting guide for new plugin devs:
|
||||
### Title
|
||||
|
||||
- Check if [someone already developed a plugin for what you want](https://obsidian.md/plugins)! There might be an existing plugin similar enough that you can partner up with.
|
||||
- Make a copy of this repo as a template with the "Use this template" button (login to GitHub if you don't see it).
|
||||
- Clone your repo to a local development folder. For convenience, you can place this folder in your `.obsidian/plugins/your-plugin-name` folder.
|
||||
- Install NodeJS, then run `npm i` in the command line under your repo folder.
|
||||
- Run `npm run dev` to compile your plugin from `main.ts` to `main.js`.
|
||||
- Make changes to `main.ts` (or create new `.ts` files). Those changes should be automatically compiled into `main.js`.
|
||||
- Reload Obsidian to load the new version of your plugin.
|
||||
- Enable plugin in settings window.
|
||||
- For updates to the Obsidian API run `npm update` in the command line under your repo folder.
|
||||
You can add title to the code block like this.
|
||||
|
||||
## Releasing new releases
|
||||
```python title:"Title of the code block"
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
- Update your `manifest.json` with your new version number, such as `1.0.1`, and the minimum Obsidian version required for your latest release.
|
||||
- Update your `versions.json` file with `"new-plugin-version": "minimum-obsidian-version"` so older versions of Obsidian can download an older version of your plugin that's compatible.
|
||||
- Create new GitHub release using your new version number as the "Tag version". Use the exact version number, don't include a prefix `v`. See here for an example: https://github.com/obsidianmd/obsidian-sample-plugin/releases
|
||||
- Upload the files `manifest.json`, `main.js`, `styles.css` as binary attachments. Note: The manifest.json file must be in two places, first the root path of your repository and also in the release.
|
||||
- Publish the release.
|
||||

|
||||
|
||||
> You can simplify the version bump process by running `npm version patch`, `npm version minor` or `npm version major` after updating `minAppVersion` manually in `manifest.json`.
|
||||
> The command will bump version in `manifest.json` and `package.json`, and add the entry for the new version to `versions.json`
|
||||
You must put the title in quotes. You can use both single and double quotes.
|
||||
|
||||
## Adding your plugin to the community plugin list
|
||||
### Line Numbers
|
||||
|
||||
- Check https://github.com/obsidianmd/obsidian-releases/blob/master/plugin-review.md
|
||||
- Publish an initial version.
|
||||
- Make sure you have a `README.md` file in the root of your repo.
|
||||
- Make a pull request at https://github.com/obsidianmd/obsidian-releases to add your plugin.
|
||||
You can add line numbers to the code block like this.
|
||||
|
||||
## How to use
|
||||
```python linenos
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
- Clone this repo.
|
||||
- `npm i` or `yarn` to install dependencies
|
||||
- `npm run dev` to start compilation in watch mode.
|
||||
Or like this.
|
||||
|
||||
## Manually installing the plugin
|
||||
```python linenos:true
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
- Copy over `main.js`, `styles.css`, `manifest.json` to your vault `VaultFolder/.obsidian/plugins/your-plugin-id/`.
|
||||

|
||||
|
||||
## Improve code quality with eslint (optional)
|
||||
- [ESLint](https://eslint.org/) is a tool that analyzes your code to quickly find problems. You can run ESLint against your plugin to find common bugs and ways to improve your code.
|
||||
- To use eslint with this project, make sure to install eslint from terminal:
|
||||
- `npm install -g eslint`
|
||||
- To use eslint to analyze this project use this command:
|
||||
- `eslint main.ts`
|
||||
- eslint will then create a report with suggestions for code improvement by file and line number.
|
||||
- If your source code is in a folder, such as `src`, you can use eslint with this command to analyze all files in that folder:
|
||||
- `eslint .\src\`
|
||||
If you don't specify any options, the line numbers will start from the value set in the settings (default: 1). If you want the line numbers to start from a specific value, you can do it like this.
|
||||
|
||||
## Funding URL
|
||||
```python linenos:10
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
You can include funding URLs where people who use your plugin can financially support it.
|
||||

|
||||
|
||||
The simple way is to set the `fundingUrl` field to your link in your `manifest.json` file:
|
||||
You can also use negative values.
|
||||
|
||||
```json
|
||||
{
|
||||
"fundingUrl": "https://buymeacoffee.com"
|
||||
}
|
||||
```
|
||||
```python linenos:-10
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
If you have multiple URLs, you can also do:
|
||||

|
||||
|
||||
```json
|
||||
{
|
||||
"fundingUrl": {
|
||||
"Buy Me a Coffee": "https://buymeacoffee.com",
|
||||
"GitHub Sponsor": "https://github.com/sponsors",
|
||||
"Patreon": "https://www.patreon.com/"
|
||||
}
|
||||
}
|
||||
```
|
||||
If you want to explicitly hide the line numbers, you can do it like this.
|
||||
|
||||
## API Documentation
|
||||
```python linenos:false
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
See https://github.com/obsidianmd/obsidian-api
|
||||
### Line Highlight
|
||||
|
||||
You can highlight specific lines in the code block with commas like this.
|
||||
|
||||
```python highlight:"2,3"
|
||||
print("Hello world")
|
||||
print("I love you")
|
||||
print("Wow")
|
||||
```
|
||||
|
||||

|
||||
|
||||
You must put the line numbers in quotes. You can use both single and double quotes.
|
||||
|
||||
If you use the line numbers feature and set the line numbers don't start from 1, you must enter the line numbers to be highlighted according to that.
|
||||
|
||||
```python linenos:10 highlight:"10,11"
|
||||
print("Hello world")
|
||||
print("I love you")
|
||||
print("Wow")
|
||||
```
|
||||
|
||||

|
||||
|
||||
### Language Indicator
|
||||
|
||||
You can add the language indicator to the code block like this.
|
||||
|
||||
```python language
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
Or like this.
|
||||
|
||||
```python language:true
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||

|
||||
|
||||
The displayed language is following the language of the code block by default, but if you want, you can specify it explicitly. In this case, you can change the language displayed while keeping the syntax highlighting done by the language of the code block.
|
||||
|
||||
```python language:"javascript"
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||

|
||||
|
||||
You must put the language in quotes. You can use both single and double quotes.
|
||||
|
||||
If neither the language of the code block nor the language specified by the language option is specified, the language displayed is the value set in the settings (default: "plain text").
|
||||
|
||||
If you want to explicitly hide the language indicator, you can do it like this.
|
||||
|
||||
```python language:false
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
### Copy Button
|
||||
|
||||
You can add a copy button to the code block like this.
|
||||
|
||||
```python copybtn
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
Or like this.
|
||||
|
||||
```python copybtn:true
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||

|
||||
|
||||
If you want to explicitly hide the copy button, you can do it like this.
|
||||
|
||||
```python copybtn:false
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
### Prompt
|
||||
|
||||
You can add a prompt to the code block like this.
|
||||
|
||||
```bash prompt
|
||||
mkdir test
|
||||
```
|
||||
|
||||
Or like this.
|
||||
|
||||
```bash prompt:true
|
||||
mkdir test
|
||||
```
|
||||
|
||||

|
||||
|
||||
If you don't specify any options, the prompt will be the value set in the settings (default: "$"). If you want to use another prompt, you can do it like this.
|
||||
|
||||
```bash prompt:"#"
|
||||
mkdir test
|
||||
```
|
||||
|
||||

|
||||
|
||||
You must put the prompt in quotes. You can use both single and double quotes.
|
||||
|
||||
If there is a "\" character at the end of the line, the prompt will not be displayed on the next line.
|
||||
|
||||
```bash prompt
|
||||
docker run -it \
|
||||
ubuntu:latest
|
||||
```
|
||||
|
||||

|
||||
|
||||
If you want to explicitly hide the prompt, you can do it like this.
|
||||
|
||||
```bash prompt:false
|
||||
mkdir test
|
||||
```
|
||||
|
||||
### Result
|
||||
|
||||
You can add a result to the code block like this.
|
||||
|
||||
```python
|
||||
print("Hello world!")
|
||||
```
|
||||
|
||||
```result
|
||||
Hello world!
|
||||
```
|
||||
|
||||

|
||||
|
||||
Note that the Result Code Block must be placed immediately after the Code Block to which the result belongs. You must not put any other components between them.
|
||||
|
||||
Also note that the result code block is not dynamically generated. Result code blocks are just simple plain-text code blocks. You must enter the result manually.
|
||||
|
||||
You can use line numbers, line highlight features only in the result code block. Other features will be ignored even if you specify them.
|
||||
|
||||
```python
|
||||
print("Hello world!")
|
||||
```
|
||||
|
||||
```result linenos
|
||||
Hello world!
|
||||
```
|
||||
|
||||
```result highlight:"1"
|
||||
Hello world!
|
||||
```
|
||||
|
||||

|
||||
|
||||
The prompt of the result code block is the value set in the settings by default (default: "Result"). If you want to use another prompt, you can do it like this.
|
||||
|
||||
```python
|
||||
import random
|
||||
print(random.randint(1, 100))
|
||||
```
|
||||
|
||||
```result prompt:"Result (may be different depending on your environment)"
|
||||
42
|
||||
```
|
||||
|
||||

|
||||
|
||||
## Settings
|
||||
|
||||
You can change the default settings of each feature of HK Code Block in the settings.
|
||||
|
||||
### Title
|
||||
|
||||
- Use Title
|
||||
- default off, but on when specified : the title will only be shown if the title statement is specified
|
||||
- always off : the title will never be shown
|
||||
|
||||
### Line numbers
|
||||
|
||||
- Use Line Numbers
|
||||
- always on : line numbers will always be shown
|
||||
- default on, but off when specified : line numbers will always be shown, but will not be shown if linenos statement is specified as false
|
||||
- default off, but on when specified : line numbers will only be shown if the linenos statement is specified as true
|
||||
- always off : line numbers will never be shown
|
||||
- Default Line Number Start : the default line number start of the code block (default: 1)
|
||||
- Show Line Number Splitter : whether to show the line number splitter (default: true)
|
||||
|
||||
### Line Highlight
|
||||
|
||||
- Use Line Highlight
|
||||
- default off, but on when specified : line highlight will only be shown if the line highlight statement is specified
|
||||
- always off : line highlight will never be shown
|
||||
- Line Highlight Color : The background color of the highlighted line. Note that the given color will be applied with 0.2 opacity. (default: #ff0000)
|
||||
|
||||
### Language Indicator
|
||||
|
||||
- Use Language Indicator
|
||||
- always on : language indicator will always be shown
|
||||
- default on, but off when specified : language indicator will always be shown, but will not be shown if the language indicator statement is specified as false
|
||||
- default off, but on when specified : language indicator will only be shown if the language indicator statement is specified as true
|
||||
- always off : language indicator will never be shown
|
||||
- Default Language : the default language name of the code block if the language is not specified. (default: "plain text")
|
||||
|
||||
### Copy Button
|
||||
|
||||
- Use Copy Button
|
||||
- always on : copy button will always be shown
|
||||
- default on, but off when specified : copy button will always be shown, but will not be shown if the copy button statement is specified as false
|
||||
- default off, but on when specified : copy button will only be shown if the copy button statement is specified as true
|
||||
- always off : copy button will never be shown
|
||||
|
||||
### Prompt
|
||||
|
||||
- Use Prompt
|
||||
- always on : prompt will always be shown for languages specified in the "Prompting Languages" option
|
||||
- default on, but off when specified : prompt will always be shown for languages specified in the "Prompting Languages" option, but will not be shown if the prompt statement is specified as false
|
||||
- default off, but on when specified : prompt will only be shown if the prompt statement is specified as true
|
||||
- always off : prompt will never be shown
|
||||
- Prompting Languages : The languages that will be prompted by default. You can specify multiple languages by separating them with enters. (default: "bash")
|
||||
- Default Prompt : The default prompt of the code block. (default: "$")
|
||||
|
||||
### Result
|
||||
|
||||
- Use Result
|
||||
- enable : use the result feature
|
||||
- disable : do not use the result feature
|
||||
- Default Result Prompt : The default prompt of the result code block. (default: "Result")
|
||||
|
||||
### Developers
|
||||
|
||||
- Debug Mode
|
||||
- true : enable debug mode. Log messages will be displayed in the console.
|
||||
- false : disable debug mode.
|
||||
|
||||
## License
|
||||
|
||||
MIT License
|
||||
|
||||
## Known Issues
|
||||
|
||||
- Some features may not work properly depending on the theme you are using. Currently on 2023-02, I checked that the following themes are working properly.
|
||||
- Default Theme
|
||||
- [Minimal Theme](https://github.com/kepano/obsidian-minimal)
|
||||
- [Obsidian Nord](https://github.com/insanum/obsidian_nord)
|
||||
- [Obsidianite](https://github.com/bennyxguo/Obsidian-Obsidianite)
|
||||
- [Sanctum](https://github.com/jdanielmourao/obsidian-sanctum)
|
||||
- [Shimmering Focus](https://github.com/chrisgrieser/shimmering-focus)
|
||||
- [Wasp](https://github.com/santiyounger/Wasp-Obsidian-Theme)
|
||||
- [Typewriter](https://github.com/crashmoney/obsidian-typewriter)
|
||||
|
||||
## Changelog
|
||||
|
||||
### 0.1.0
|
||||
|
||||
- Initial release
|
||||
318
README_ko.md
Normal file
|
|
@ -0,0 +1,318 @@
|
|||
# HK CodeBlock
|
||||
|
||||
[Obsidian](https://obsidian.md) plugin developed by Heekang Park
|
||||
|
||||
[ 한국어 | [English](https://github.com/HeekangPark/obsidian-hk-code-block/blob/master/README.md) ]
|
||||
|
||||
HK CodeBlock을 이용하면 Code Block에 다양한 기능들을 추가할 수 있습니다.
|
||||
|
||||
[Prism.js](https://prismjs.com/)의 플러그인, [Better Code Block](https://github.com/stargrey/obsidian-better-codeblock), [Code Block Enhancer](https://github.com/nyable/obsidian-code-block-enhancer) 등에서 영감을 받아 만들었습니다.
|
||||
|
||||
## 기능
|
||||
|
||||
### 제목
|
||||
|
||||
다음과 같이 하면 Code Block에 제목을 설정할 수 있습니다.
|
||||
|
||||
```python title:"Title of the code block"
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||

|
||||
|
||||
반드시 따옴표 안에 제목을 넣어야 합니다. 큰따옴표와 작은 따옴표 모두 사용 가능합니다.
|
||||
|
||||
### 줄 번호
|
||||
|
||||
다음과 같이 하면 Code Block에 줄 번호를 추가할 수 있습니다.
|
||||
|
||||
```python linenos
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
또는 다음과 같이 할 수도 있습니다.
|
||||
|
||||
```python linenos:true
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||

|
||||
|
||||
아무런 옵션을 주지 않으면 줄 번호는 설정에서 설정한 값(기본값: 1)부터 시작합니다. 줄 번호가 특정 값으로 시작하도록 하려면 다음과 같이 하면 됩니다.
|
||||
|
||||
```python linenos:10
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||

|
||||
|
||||
음수 값도 가능합니다.
|
||||
|
||||
```python linenos:-10
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||

|
||||
|
||||
다음과 같이 하면 명시적으로 줄 번호를 숨길 수 있습니다.
|
||||
|
||||
```python linenos:false
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
### 줄 강조
|
||||
|
||||
다음과 같이 강조하고 싶은 줄의 번호를 콤마로 구분하여 입력하면 해당 줄에 강조 효과를 줄 수 있습니다.
|
||||
|
||||
```python highlight:"2,3"
|
||||
print("Hello world")
|
||||
print("I love you")
|
||||
print("Wow")
|
||||
```
|
||||
|
||||

|
||||
|
||||
반드시 따옴표 안에 줄 번호를 넣어야 합니다. 큰따옴표와 작은 따옴표 모두 사용 가능합니다.
|
||||
|
||||
만약 줄 번호 기능을 사용해 줄 번호가 1부터 시작하지 않도록 했다면, 강조할 줄 번호를 입력할 때도 이에 맞춰서 입력해야 합니다.
|
||||
|
||||
```python linenos:10 highlight:"10,11"
|
||||
print("Hello world")
|
||||
print("I love you")
|
||||
print("Wow")
|
||||
```
|
||||
|
||||

|
||||
|
||||
### 언어 표시
|
||||
|
||||
다음과 같이 하면 Code Block에 Code Block의 언어를 표시할 수 있습니다.
|
||||
|
||||
```python language
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
또는 다음과 같이 할 수도 있습니다.
|
||||
|
||||
```python language:true
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||

|
||||
|
||||
표시되는 언어는 기본적으로 Code Block의 언어를 따라가지만, 만약 원한다면 직접 명시할 수 있습니다. 이렇게 하면 구문 강조(syntax highlighting)는 Code Block의 언어를 따라가게 하면서 표시되는 언어만 바꿀 수 있습니다.
|
||||
|
||||
```python language:"javascript"
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||

|
||||
|
||||
언어를 직접 명시할 때는 반드시 따옴표 안에 언어를 넣어야 합니다. 큰따옴표와 작은 따옴표 모두 사용 가능합니다.
|
||||
|
||||
만약 Code Block의 언어도, language 옵션으로 명시한 언어도 없다면, 언어 표시는 설정에서 설정된 값(기본값: "plain text")이 적용됩니다.
|
||||
|
||||
다음과 같이 하면 명시적으로 언어를 숨길 수 있습니다.
|
||||
|
||||
```python language:false
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
### 복사 버튼
|
||||
|
||||
다음과 같이 하면 Code Block에 복사 버튼을 추가할 수 있습니다.
|
||||
|
||||
```python copybtn
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
또는 다음과 같이 할 수도 있습니다.
|
||||
|
||||
```python copybtn:true
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||

|
||||
|
||||
다음과 같이 하면 명시적으로 복사 버튼을 숨길 수 있습니다.
|
||||
|
||||
```python copybtn:false
|
||||
print("Hello world")
|
||||
```
|
||||
|
||||
### 프롬프트
|
||||
|
||||
다음과 같이 하면 Code Block에 프롬프트를 추가할 수 있습니다.
|
||||
|
||||
```bash prompt
|
||||
mkdir test
|
||||
```
|
||||
|
||||
또는 다음과 같이 할 수도 있습니다.
|
||||
|
||||
```bash prompt:true
|
||||
mkdir test
|
||||
```
|
||||
|
||||

|
||||
|
||||
아무런 옵션을 주지 않으면 프롬프트는 설정에서 설정한 값(기본값: "$")이 사용됩니다. 다른 프롬프트를 사용하고 싶다면 다음과 같이 하면 됩니다.
|
||||
|
||||
```bash prompt:"#"
|
||||
mkdir test
|
||||
```
|
||||
|
||||

|
||||
|
||||
프롬프트를 직접 명시할 때는 반드시 따옴표 안에 프롬프트를 넣어야 합니다. 큰따옴표와 작은 따옴표 모두 사용 가능합니다.
|
||||
|
||||
줄 끝에 "\" 문자가 있는 경우 다음 줄에는 프롬프트가 표시되지 않습니다.
|
||||
|
||||
```bash prompt
|
||||
docker run -it \
|
||||
ubuntu:latest
|
||||
```
|
||||
|
||||

|
||||
|
||||
다음과 같이 하면 명시적으로 프롬프트를 숨길 수 있습니다.
|
||||
|
||||
```bash prompt:false
|
||||
mkdir test
|
||||
```
|
||||
|
||||
### 실행 결과 표시
|
||||
|
||||
다음과 같이 하면 Code Block의 실행 결과를 표시할 수 있습니다.
|
||||
|
||||
```python
|
||||
print("Hello world!")
|
||||
```
|
||||
|
||||
```result
|
||||
Hello world!
|
||||
```
|
||||
|
||||

|
||||
|
||||
Result Code Block은 반드시 실행 결과를 표시할 Code Block 바로 다음에 위치해야 합니다. 두 Code Block 사이에 어떠한 요소도 있으면 안됩니다.
|
||||
|
||||
또 Result Code Block은 Code Block을 실행해 동적으로 생성되는 그런 것이 아닙니다. Result Code Block은 단순한 텍스트 Code Block(plain-text code block)일 뿐입니다. 사용자가 직접 실행 결과를 입력해야 합니다.
|
||||
|
||||
Result Code Block에서는 줄 번호, 줄 강조 기능만 사용 가능합니다. 나머지 기능들은 명시해도 무시됩니다.
|
||||
|
||||
```python
|
||||
print("Hello world!")
|
||||
```
|
||||
|
||||
```result linenos
|
||||
Hello world!
|
||||
```
|
||||
|
||||
```result highlight:"1"
|
||||
Hello world!
|
||||
```
|
||||
|
||||

|
||||
|
||||
Result Code Block의 프롬프트는 설정에서 설정한 값(기본값: "Result")이 사용됩니다. 다른 프롬프트를 사용하고 싶다면 다음과 같이 하면 됩니다.
|
||||
|
||||
```python
|
||||
import random
|
||||
print(random.randint(1, 100))
|
||||
```
|
||||
|
||||
```result prompt:"Result (may be different depending on your environment)"
|
||||
42
|
||||
```
|
||||
|
||||

|
||||
|
||||
## 설정
|
||||
|
||||
설정에서는 HK Code Block의 각 기능별 기본 설정을 변경할 수 있습니다.
|
||||
|
||||
### Title
|
||||
|
||||
- Use Title
|
||||
- default off, but on when specified : 기본적으로 제목 기능을 사용하지 않습니다. 하지만 title 옵션을 명시하면 제목 기능을 사용합니다.
|
||||
- always off : 제목 기능을 항상 사용하지 않습니다.
|
||||
|
||||
### Line numbers
|
||||
|
||||
- Use Line Numbers
|
||||
- always on : 줄 번호 기능을 항상 사용합니다.
|
||||
- default on, but off when specified : 기본적으로 줄 번호 기능을 사용합니다. 하지만 줄 번호 기능을 사용하지 않는다고 명시하면 줄 번호 기능을 사용하지 않습니다.
|
||||
- default off, but on when specified : 기본적으로 줄 번호 기능을 사용하지 않습니다. 하지만 줄 번호 기능을 사용한다고 명시하면 줄 번호 기능을 사용합니다.
|
||||
- always off : 줄 번호 기능을 항상 사용하지 않습니다.
|
||||
- Default Line Number Start : 줄의 시작 번호를 설정합니다. 기본값은 1입니다.
|
||||
- Show Line Number Splitter : 줄 번호와 코드 사이에 구분선을 표시합니다. 기본값은 true입니다.
|
||||
|
||||
### Line Highlight
|
||||
|
||||
- Use Line Highlight
|
||||
- default off, but on when specified : 기본적으로 줄 강조 기능을 사용하지 않습니다. 하지만 줄 강조 기능을 사용한다고 명시하면 줄 강조 기능을 사용합니다.
|
||||
- always off : 줄 강조 기능을 항상 사용하지 않습니다.
|
||||
- Line Highlight Color : 강조색을 설정합니다. 기본값은 #ff0000입니다. 실제 줄 강조에는 여기 명시된 색에 opacity 0.2가 적용된다는 점에 유의하세요.
|
||||
|
||||
### Language Indicator
|
||||
|
||||
- Use Language Indicator
|
||||
- always on : 언어 표시 기능을 항상 사용합니다.
|
||||
- default on, but off when specified : 기본적으로 언어 표시 기능을 사용합니다. 하지만 언어 표시 기능을 사용하지 않는다고 명시하면 언어 표시 기능을 사용하지 않습니다.
|
||||
- default off, but on when specified : 기본적으로 언어 표시 기능을 사용하지 않습니다. 하지만 언어 표시 기능을 사용한다고 명시하면 언어 표시 기능을 사용합니다.
|
||||
- always off : 언어 표시 기능을 항상 사용하지 않습니다.
|
||||
- Default Language : 언어 표시 기능을 사용할 때 아무런 언어가 명시되지 않았다면 기본으로 사용할 언어를 설정합니다. 기본값은 "plain text"입니다.
|
||||
|
||||
### Copy Button
|
||||
|
||||
- Use Copy Button
|
||||
- always on : 복사 버튼 기능을 항상 사용합니다.
|
||||
- default on, but off when specified : 기본적으로 복사 버튼 기능을 사용합니다. 하지만 복사 버튼 기능을 사용하지 않는다고 명시하면 복사 버튼 기능을 사용하지 않습니다.
|
||||
- default off, but on when specified : 기본적으로 복사 버튼 기능을 사용하지 않습니다. 하지만 복사 버튼 기능을 사용한다고 명시하면 복사 버튼 기능을 사용합니다.
|
||||
- always off : 복사 버튼 기능을 항상 사용하지 않습니다.
|
||||
|
||||
### Prompt
|
||||
|
||||
- Use Prompt
|
||||
- always on : Prompting Languages에 명시된 언어들에 대해, 프롬프트 기능을 항상 사용합니다.
|
||||
- default on, but off when specified : Prompting Languages에 명시된 언어들에 대해, 기본적으로 프롬프트 기능을 사용합니다. 하지만 프롬프트 기능을 사용하지 않는다고 명시하면 프롬프트 기능을 사용하지 않습니다.
|
||||
- default off, but on when specified : 기본적으로 프롬프트 기능을 사용하지 않습니다. 하지만 프롬프트 기능을 사용한다고 명시하면 프롬프트 기능을 사용합니다.
|
||||
- always off : 프롬프트 기능을 항상 사용하지 않습니다.
|
||||
- Prompting Languages : 프롬프트 기능을 기본적으로 사용할 언어를 설정합니다. 엔터 키로 구분해 여러 개의 언어를 설정할 수 있습니다. 기본값은 "bash"입니다.
|
||||
- Default Prompt : 프롬프트 기능을 사용할 때 아무런 프롬프트가 명시되지 않았다면 기본으로 사용할 프롬프트를 설정합니다. 기본값은 "$"입니다.
|
||||
|
||||
### Result
|
||||
|
||||
- Use Result
|
||||
- enable : 실행 결과 표시 기능을 사용합니다.
|
||||
- disable : 실행 결과 표시 기능을 사용하지 않습니다.
|
||||
- Default Result Prompt : 실행 결과 표시 Code Block의 프롬프트를 설정합니다. 기본값은 "Result"입니다.
|
||||
|
||||
### Developers
|
||||
|
||||
- Debug Mode
|
||||
- true : 디버그 모드를 활성화해 log를 console에 출력합니다.
|
||||
- false : 디버그 모드를 비활성화합니다.
|
||||
|
||||
## 라이센스
|
||||
|
||||
MIT License
|
||||
|
||||
## 알려진 문제점
|
||||
|
||||
- 테마별 설정에 따라 몇몇 기능들이 제대로 동작하지 않을 수 있습니다. 2023년 02월 현재, 정상 동작을 시험해본 테마는 다음과 같습니다.
|
||||
- Default Theme
|
||||
- [Minimal Theme](https://github.com/kepano/obsidian-minimal)
|
||||
- [Obsidian Nord](https://github.com/insanum/obsidian_nord)
|
||||
- [Obsidianite](https://github.com/bennyxguo/Obsidian-Obsidianite)
|
||||
- [Sanctum](https://github.com/jdanielmourao/obsidian-sanctum)
|
||||
- [Shimmering Focus](https://github.com/chrisgrieser/shimmering-focus)
|
||||
- [Wasp](https://github.com/santiyounger/Wasp-Obsidian-Theme)
|
||||
- [Typewriter](https://github.com/crashmoney/obsidian-typewriter)
|
||||
|
||||
## Changelog
|
||||
|
||||
### 0.1.0
|
||||
|
||||
- 최초 배포
|
||||
674
dist/main.js
vendored
Normal file
11
dist/manifest.json
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"id": "obsidian-hk-code-block",
|
||||
"name": "HK Code Block",
|
||||
"version": "0.1.0",
|
||||
"minAppVersion": "1.0.0",
|
||||
"description": "Obsidian plugin developed by Heekang Park; Make code block looking good on reading view",
|
||||
"author": "Heekang Park",
|
||||
"authorUrl": "https://github.com/HeekangPark",
|
||||
"fundingUrl": "",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
140
dist/styles.css
vendored
Normal file
3
dist/versions.json
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"0.1.0": "0.15.0"
|
||||
}
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
import esbuild from "esbuild";
|
||||
import process from "process";
|
||||
import builtins from "builtin-modules";
|
||||
import { sassPlugin } from "esbuild-sass-plugin";
|
||||
|
||||
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
|
||||
*/
|
||||
|
|
@ -15,7 +16,10 @@ const context = await esbuild.context({
|
|||
banner: {
|
||||
js: banner,
|
||||
},
|
||||
entryPoints: ["main.ts"],
|
||||
entryPoints: [
|
||||
"./src/styles.scss",
|
||||
"./src/main.ts"
|
||||
],
|
||||
bundle: true,
|
||||
external: [
|
||||
"obsidian",
|
||||
|
|
@ -37,7 +41,8 @@ const context = await esbuild.context({
|
|||
logLevel: "info",
|
||||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "main.js",
|
||||
outdir: "./dist",
|
||||
plugins: [sassPlugin()]
|
||||
});
|
||||
|
||||
if (prod) {
|
||||
|
|
|
|||
BIN
imgs/copybtn.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
imgs/highlight1.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
BIN
imgs/highlight2.png
Normal file
|
After Width: | Height: | Size: 8.7 KiB |
BIN
imgs/language1.png
Normal file
|
After Width: | Height: | Size: 5.8 KiB |
BIN
imgs/language2.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
imgs/linenos1.png
Normal file
|
After Width: | Height: | Size: 6 KiB |
BIN
imgs/linenos2.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
imgs/linenos3.png
Normal file
|
After Width: | Height: | Size: 6.2 KiB |
BIN
imgs/prompt1.png
Normal file
|
After Width: | Height: | Size: 5.1 KiB |
BIN
imgs/prompt2.png
Normal file
|
After Width: | Height: | Size: 5 KiB |
BIN
imgs/prompt3.png
Normal file
|
After Width: | Height: | Size: 6.3 KiB |
BIN
imgs/result1.png
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
BIN
imgs/result2.png
Normal file
|
After Width: | Height: | Size: 9.1 KiB |
BIN
imgs/result3.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
imgs/title.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
137
main.ts
|
|
@ -1,137 +0,0 @@
|
|||
import { App, Editor, MarkdownView, Modal, Notice, Plugin, PluginSettingTab, Setting } from 'obsidian';
|
||||
|
||||
// Remember to rename these classes and interfaces!
|
||||
|
||||
interface MyPluginSettings {
|
||||
mySetting: string;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: MyPluginSettings = {
|
||||
mySetting: 'default'
|
||||
}
|
||||
|
||||
export default class MyPlugin extends Plugin {
|
||||
settings: MyPluginSettings;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
|
||||
// This creates an icon in the left ribbon.
|
||||
const ribbonIconEl = this.addRibbonIcon('dice', 'Sample Plugin', (evt: MouseEvent) => {
|
||||
// Called when the user clicks the icon.
|
||||
new Notice('This is a notice!');
|
||||
});
|
||||
// Perform additional things with the ribbon
|
||||
ribbonIconEl.addClass('my-plugin-ribbon-class');
|
||||
|
||||
// This adds a status bar item to the bottom of the app. Does not work on mobile apps.
|
||||
const statusBarItemEl = this.addStatusBarItem();
|
||||
statusBarItemEl.setText('Status Bar Text');
|
||||
|
||||
// This adds a simple command that can be triggered anywhere
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-simple',
|
||||
name: 'Open sample modal (simple)',
|
||||
callback: () => {
|
||||
new SampleModal(this.app).open();
|
||||
}
|
||||
});
|
||||
// This adds an editor command that can perform some operation on the current editor instance
|
||||
this.addCommand({
|
||||
id: 'sample-editor-command',
|
||||
name: 'Sample editor command',
|
||||
editorCallback: (editor: Editor, view: MarkdownView) => {
|
||||
console.log(editor.getSelection());
|
||||
editor.replaceSelection('Sample Editor Command');
|
||||
}
|
||||
});
|
||||
// This adds a complex command that can check whether the current state of the app allows execution of the command
|
||||
this.addCommand({
|
||||
id: 'open-sample-modal-complex',
|
||||
name: 'Open sample modal (complex)',
|
||||
checkCallback: (checking: boolean) => {
|
||||
// Conditions to check
|
||||
const markdownView = this.app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (markdownView) {
|
||||
// If checking is true, we're simply "checking" if the command can be run.
|
||||
// If checking is false, then we want to actually perform the operation.
|
||||
if (!checking) {
|
||||
new SampleModal(this.app).open();
|
||||
}
|
||||
|
||||
// This command will only show up in Command Palette when the check function returns true
|
||||
return true;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// This adds a settings tab so the user can configure various aspects of the plugin
|
||||
this.addSettingTab(new SampleSettingTab(this.app, this));
|
||||
|
||||
// If the plugin hooks up any global DOM events (on parts of the app that doesn't belong to this plugin)
|
||||
// Using this function will automatically remove the event listener when this plugin is disabled.
|
||||
this.registerDomEvent(document, 'click', (evt: MouseEvent) => {
|
||||
console.log('click', evt);
|
||||
});
|
||||
|
||||
// When registering intervals, this function will automatically clear the interval when the plugin is disabled.
|
||||
this.registerInterval(window.setInterval(() => console.log('setInterval'), 5 * 60 * 1000));
|
||||
}
|
||||
|
||||
onunload() {
|
||||
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
|
||||
class SampleModal extends Modal {
|
||||
constructor(app: App) {
|
||||
super(app);
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const {contentEl} = this;
|
||||
contentEl.setText('Woah!');
|
||||
}
|
||||
|
||||
onClose() {
|
||||
const {contentEl} = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
}
|
||||
|
||||
class SampleSettingTab extends PluginSettingTab {
|
||||
plugin: MyPlugin;
|
||||
|
||||
constructor(app: App, plugin: MyPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const {containerEl} = this;
|
||||
|
||||
containerEl.empty();
|
||||
|
||||
containerEl.createEl('h2', {text: 'Settings for my awesome plugin.'});
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Setting #1')
|
||||
.setDesc('It\'s a secret')
|
||||
.addText(text => text
|
||||
.setPlaceholder('Enter your secret')
|
||||
.setValue(this.plugin.settings.mySetting)
|
||||
.onChange(async (value) => {
|
||||
console.log('Secret: ' + value);
|
||||
this.plugin.settings.mySetting = value;
|
||||
await this.plugin.saveSettings();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
{
|
||||
"id": "obsidian-sample-plugin",
|
||||
"name": "Sample Plugin",
|
||||
"version": "1.0.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "This is a sample plugin for Obsidian. This plugin demonstrates some of the capabilities of the Obsidian API.",
|
||||
"author": "Obsidian",
|
||||
"authorUrl": "https://obsidian.md",
|
||||
"fundingUrl": "https://obsidian.md/pricing",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
4582
package-lock.json
generated
Normal file
18
package.json
|
|
@ -1,15 +1,20 @@
|
|||
{
|
||||
"name": "obsidian-sample-plugin",
|
||||
"version": "1.0.0",
|
||||
"description": "This is a sample plugin for Obsidian (https://obsidian.md)",
|
||||
"name": "obsidian-hk-code-block",
|
||||
"version": "0.1.0",
|
||||
"description": "Obsidian plugin developed by Heekang Park; Make code block looking good on reading view",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"dev": "node esbuild.config.mjs",
|
||||
"build": "tsc -noEmit -skipLibCheck && node esbuild.config.mjs production",
|
||||
"version": "node version-bump.mjs && git add manifest.json versions.json"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "",
|
||||
"keywords": [
|
||||
"Obsidian",
|
||||
"Code Block",
|
||||
"code-block",
|
||||
"CodeBlock"
|
||||
],
|
||||
"author": "Heekang Park",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.6",
|
||||
|
|
@ -19,6 +24,7 @@
|
|||
"esbuild": "0.17.3",
|
||||
"obsidian": "latest",
|
||||
"tslib": "2.4.0",
|
||||
"typescript": "4.7.4"
|
||||
"typescript": "4.7.4",
|
||||
"esbuild-sass-plugin": "^2.4.5"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
343
src/HKCodeBlockProcessor.ts
Executable file
|
|
@ -0,0 +1,343 @@
|
|||
import { MarkdownView } from "obsidian";
|
||||
import type { MarkdownPostProcessorContext, MarkdownSectionInformation } from "obsidian";
|
||||
|
||||
import HKCodeBlockPlugin from "./main";
|
||||
import type { HKCodeBlockSettings } from "./HKCodeBlockSettings";
|
||||
|
||||
const REGEX = {
|
||||
TITLE: /\stitle:(?:'(.*?)'|"(.*?)")\s/,
|
||||
LINENOS_SIMPLE: /\slinenos\s/,
|
||||
LINENOS_COMPLICATE: /\slinenos:(true|false|(-{0,1}\d+))\s/,
|
||||
HIGHLIGHT: /\shighlight:(?:'(.*?)'|"(.*?)")\s/,
|
||||
LANGUAGE_FROM_CLASS: /^language-(\w+)$/,
|
||||
LANGUAGE_SIMPLE: /\slanguage\s/,
|
||||
LANGUAGE_COMPLICATE: /\slanguage:(true|false|'(.*?)'|"(.*?)")\s/,
|
||||
COPYBTN_SIMPLE: /\scopybtn\s/,
|
||||
COPYBTN_COMPLICATE: /\scopybtn:(true|false)\s/,
|
||||
RESULT: /^```result\s/,
|
||||
PROMPT_SIMPLE: /\sprompt\s/,
|
||||
PROMPT_COMPLICATE: /\sprompt:(true|false|'(.*?)'|"(.*?)")\s/,
|
||||
}
|
||||
|
||||
function getMeta(view: MarkdownView, elem_code: HTMLElement, section_code: MarkdownSectionInformation, settings: HKCodeBlockSettings): {
|
||||
showTitle: (boolean | undefined),
|
||||
title: string,
|
||||
showLinenos: (boolean | undefined),
|
||||
linenosStart: number,
|
||||
linenosNum: number,
|
||||
showHighlight: (boolean | undefined),
|
||||
highlightLines: number[],
|
||||
showLanguage: (boolean | undefined),
|
||||
language: string,
|
||||
showCopyBtn: (boolean | undefined),
|
||||
isResult: boolean,
|
||||
resultPrompt: string,
|
||||
showPrompt: (boolean | undefined),
|
||||
prompt: string
|
||||
} {
|
||||
const firstline = view.editor.getLine(section_code.lineStart) + " "; // add a space to the end of the line to make sure the regex works
|
||||
|
||||
// title
|
||||
let showTitle: (boolean | undefined) = undefined;
|
||||
let title = "";
|
||||
const regexResult_title = REGEX.TITLE.exec(firstline);
|
||||
if (regexResult_title) {
|
||||
showTitle = true;
|
||||
title = regexResult_title[1] || regexResult_title[2];
|
||||
}
|
||||
|
||||
// linenos
|
||||
let showLinenos: (boolean | undefined) = undefined;
|
||||
let linenosStart = settings.defaultLinenosStart;
|
||||
const linenosNum = section_code.lineEnd - section_code.lineStart - 1;
|
||||
const regexResult_linenosSimple = REGEX.LINENOS_SIMPLE.exec(firstline);
|
||||
const regexResult_linenosComplicate = REGEX.LINENOS_COMPLICATE.exec(firstline);
|
||||
if (regexResult_linenosSimple) {
|
||||
showLinenos = true;
|
||||
} else if (regexResult_linenosComplicate) {
|
||||
if (regexResult_linenosComplicate[1] === "true") {
|
||||
showLinenos = true;
|
||||
} else if (regexResult_linenosComplicate[1] === "false") {
|
||||
showLinenos = false;
|
||||
} else {
|
||||
showLinenos = true;
|
||||
linenosStart = parseInt(regexResult_linenosComplicate[2]) || 1;
|
||||
}
|
||||
}
|
||||
|
||||
// highlight
|
||||
let showHighlight: (boolean | undefined) = undefined;
|
||||
let highlightLines: number[] = [];
|
||||
const regexResult_highlight = REGEX.HIGHLIGHT.exec(firstline);
|
||||
if (regexResult_highlight) {
|
||||
showHighlight = true;
|
||||
const highlightLines_str = regexResult_highlight[1] || regexResult_highlight[2];
|
||||
highlightLines = highlightLines_str.split(",").map((line) => parseInt(line));
|
||||
|
||||
if (highlightLines.some((line) => isNaN(line))) {
|
||||
// if any element is nan, then set showHighlight to false
|
||||
showHighlight = undefined;
|
||||
highlightLines = [];
|
||||
} else {
|
||||
// use only valid line numbers
|
||||
highlightLines = highlightLines.filter((line) => line >= linenosStart && line < linenosStart + linenosNum);
|
||||
}
|
||||
}
|
||||
|
||||
// language
|
||||
let showLanguage: (boolean | undefined) = undefined;
|
||||
let language = (() => {
|
||||
let language = settings.defaultLanguage;
|
||||
elem_code.classList.forEach((className) => {
|
||||
const regexResult_languageFromClass = REGEX.LANGUAGE_FROM_CLASS.exec(className.trim());
|
||||
if (regexResult_languageFromClass) language = regexResult_languageFromClass[1];
|
||||
});
|
||||
|
||||
return language;
|
||||
})();
|
||||
const regexResult_languageSimple = REGEX.LANGUAGE_SIMPLE.exec(firstline);
|
||||
const regexResult_languageComplicate = REGEX.LANGUAGE_COMPLICATE.exec(firstline);
|
||||
if (regexResult_languageSimple) {
|
||||
showLanguage = true;
|
||||
} else if (regexResult_languageComplicate) {
|
||||
if (regexResult_languageComplicate[1] === "true") {
|
||||
showLanguage = true;
|
||||
} else if (regexResult_languageComplicate[1] === "false") {
|
||||
showLanguage = false;
|
||||
} else {
|
||||
showLanguage = true;
|
||||
language = regexResult_languageComplicate[2] || regexResult_languageComplicate[3];
|
||||
}
|
||||
}
|
||||
|
||||
// copy button
|
||||
let showCopyBtn: (boolean | undefined) = undefined;
|
||||
const regexResult_copyBtnSimple = REGEX.COPYBTN_SIMPLE.exec(firstline);
|
||||
const regexResult_copyBtnComplicate = REGEX.COPYBTN_COMPLICATE.exec(firstline);
|
||||
if (regexResult_copyBtnSimple) {
|
||||
showCopyBtn = true;
|
||||
} else if (regexResult_copyBtnComplicate) {
|
||||
if (regexResult_copyBtnComplicate[1] === "true") {
|
||||
showCopyBtn = true;
|
||||
} else if (regexResult_copyBtnComplicate[1] === "false") {
|
||||
showCopyBtn = false;
|
||||
}
|
||||
}
|
||||
|
||||
// prompt
|
||||
let showPrompt: (boolean | undefined) = undefined;
|
||||
let prompt = settings.defaultPrompt;
|
||||
const regexResult_promptSimple = REGEX.PROMPT_SIMPLE.exec(firstline);
|
||||
const regexResult_promptComplicate = REGEX.PROMPT_COMPLICATE.exec(firstline);
|
||||
if (regexResult_promptSimple) {
|
||||
showPrompt = true;
|
||||
} else if (regexResult_promptComplicate) {
|
||||
if (regexResult_promptComplicate[1] === "true") {
|
||||
showPrompt = true;
|
||||
} else if (regexResult_promptComplicate[1] === "false") {
|
||||
showPrompt = false;
|
||||
} else {
|
||||
showPrompt = true;
|
||||
prompt = regexResult_promptComplicate[2] || regexResult_promptComplicate[3];
|
||||
}
|
||||
}
|
||||
|
||||
// result
|
||||
let isResult = false;
|
||||
let resultPrompt = settings.defaultResultPrompt;
|
||||
const regexResult_result = REGEX.RESULT.exec(firstline);
|
||||
const regexResultPrompt_result = REGEX.PROMPT_COMPLICATE.exec(firstline);
|
||||
if (regexResult_result) {
|
||||
isResult = true;
|
||||
|
||||
// override other meta data : only linenos, highlight are available
|
||||
showTitle = false;
|
||||
showLanguage = false;
|
||||
showCopyBtn = false;
|
||||
showPrompt = false;
|
||||
|
||||
if (regexResultPrompt_result) {
|
||||
resultPrompt = regexResultPrompt_result[2] || regexResultPrompt_result[3];
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
showTitle: showTitle,
|
||||
title: title,
|
||||
showLinenos: showLinenos,
|
||||
linenosStart: linenosStart,
|
||||
linenosNum: linenosNum,
|
||||
showHighlight: showHighlight,
|
||||
highlightLines: highlightLines,
|
||||
showLanguage: showLanguage,
|
||||
language: language,
|
||||
showCopyBtn: showCopyBtn,
|
||||
isResult: isResult,
|
||||
resultPrompt: resultPrompt,
|
||||
showPrompt: showPrompt,
|
||||
prompt: prompt
|
||||
};
|
||||
}
|
||||
|
||||
export async function HKCodeBlockProcessor(
|
||||
el: HTMLElement,
|
||||
context: MarkdownPostProcessorContext,
|
||||
plugin: HKCodeBlockPlugin
|
||||
) {
|
||||
const settings = plugin.settings;
|
||||
|
||||
const view = app.workspace.getActiveViewOfType(MarkdownView);
|
||||
if (!view) return;
|
||||
|
||||
const elem_code: (HTMLElement | null) = el.querySelector("pre:not(.frontmatter) > code");
|
||||
if (!elem_code) return;
|
||||
|
||||
const elem_pre: (HTMLElement | null) = elem_code.parentElement;
|
||||
if (!elem_pre) return;
|
||||
|
||||
const elem_div: (HTMLElement | null) = elem_pre.parentElement;
|
||||
if (!elem_div) return;
|
||||
|
||||
const elem_copyBtn: (HTMLElement | null) = elem_div.querySelector(".copy-code-button");
|
||||
if (!elem_copyBtn) return;
|
||||
|
||||
const section_code: (MarkdownSectionInformation | null) = context.getSectionInfo(elem_code);
|
||||
if (!section_code) return;
|
||||
|
||||
// get metadata
|
||||
const {
|
||||
showTitle,
|
||||
title,
|
||||
showLinenos,
|
||||
linenosStart,
|
||||
linenosNum,
|
||||
showHighlight,
|
||||
highlightLines,
|
||||
showLanguage,
|
||||
language,
|
||||
showCopyBtn,
|
||||
isResult,
|
||||
resultPrompt,
|
||||
showPrompt,
|
||||
prompt
|
||||
} = getMeta(view, elem_code, section_code, settings);
|
||||
|
||||
if (settings.debugMode) {
|
||||
console.log({
|
||||
showTitle: showTitle,
|
||||
title: title,
|
||||
showLinenos: showLinenos,
|
||||
linenosStart: linenosStart,
|
||||
linenosNum: linenosNum,
|
||||
showHighlight: showHighlight,
|
||||
highlightLines: highlightLines,
|
||||
showLanguage: showLanguage,
|
||||
language: language,
|
||||
showCopyBtn: showCopyBtn,
|
||||
isResult: isResult,
|
||||
resultPrompt: resultPrompt,
|
||||
showPrompt: showPrompt,
|
||||
prompt: prompt,
|
||||
})
|
||||
}
|
||||
|
||||
// create HTML elements
|
||||
elem_div.classList.add("hk-codeblock");
|
||||
elem_code.classList.add("hk-codeblock-code");
|
||||
elem_copyBtn.remove(); // remove the original copy button as default
|
||||
|
||||
if (settings.useTitleGlobal === "default off, but on when specified" && (showTitle === true)) {
|
||||
elem_div.classList.add("hk-codeblock-show-title");
|
||||
|
||||
const elem_title = document.createElement("div");
|
||||
elem_title.classList.add("hk-codeblock-title");
|
||||
elem_title.innerHTML = title;
|
||||
elem_div.insertBefore(elem_title, elem_pre);
|
||||
}
|
||||
|
||||
if (
|
||||
(settings.useLinenosGlobal === "always on") ||
|
||||
(settings.useLinenosGlobal === "default on, but off when specified" && (showLinenos === undefined || showLinenos === true)) ||
|
||||
(settings.useLinenosGlobal === "default off, but on when specified" && (showLinenos === true))
|
||||
) {
|
||||
elem_div.classList.add("hk-codeblock-show-linenos");
|
||||
|
||||
const elem_linenos = document.createElement("code");
|
||||
elem_linenos.classList.add("hk-codeblock-linenos");
|
||||
elem_linenos.innerText = Array(linenosNum).fill(0).map((_, idx) => idx + linenosStart).join("\n");
|
||||
elem_pre.insertBefore(elem_linenos, elem_code);
|
||||
|
||||
if (settings.showLinenosSplitter) {
|
||||
elem_linenos.classList.add("show-splitter");
|
||||
}
|
||||
}
|
||||
|
||||
if (settings.useHighlightGlobal === "default off, but on when specified" && (showHighlight === true)) {
|
||||
elem_div.classList.add("hk-codeblock-show-highlight");
|
||||
|
||||
elem_div.style.setProperty("--hk-codeblock-highlight-background-color", settings.highlightColor);
|
||||
|
||||
const elem_highlight = document.createElement("code");
|
||||
elem_highlight.classList.add("hk-codeblock-highlight");
|
||||
elem_highlight.innerHTML = Array(linenosNum).fill(0).map((_, idx) => {
|
||||
const classNames: string[] = [];
|
||||
if (highlightLines.includes(idx + linenosStart)) classNames.push("highlight");
|
||||
return `<span class="line line-${idx + linenosStart} ${classNames.join(" ")}"> </span>`;
|
||||
}).join("\n");
|
||||
elem_pre.insertBefore(elem_highlight, elem_code);
|
||||
}
|
||||
|
||||
if (
|
||||
(settings.useLanguageGlobal === "always on") ||
|
||||
(settings.useLanguageGlobal === "default on, but off when specified" && (showLanguage === undefined || showLanguage === true)) ||
|
||||
(settings.useLanguageGlobal === "default off, but on when specified" && (showLanguage === true))
|
||||
) {
|
||||
elem_div.classList.add("hk-codeblock-show-language");
|
||||
|
||||
const elem_language = document.createElement("div");
|
||||
elem_language.classList.add("hk-codeblock-language");
|
||||
elem_language.innerText = language;
|
||||
elem_pre.prepend(elem_language);
|
||||
}
|
||||
|
||||
if (
|
||||
(settings.useCopyBtnGlobal === "always on") ||
|
||||
(settings.useCopyBtnGlobal === "default on, but off when specified" && (showCopyBtn === undefined || showCopyBtn === true)) ||
|
||||
(settings.useCopyBtnGlobal === "default off, but on when specified" && (showCopyBtn === true))
|
||||
) {
|
||||
elem_div.classList.add("hk-codeblock-show-copybtn");
|
||||
|
||||
elem_pre.appendChild(elem_copyBtn);
|
||||
}
|
||||
|
||||
if (
|
||||
(settings.usePromptGlobal === "always on" && settings.promptingLanguages.includes(language)) ||
|
||||
(settings.usePromptGlobal === "default on, but off when specified" && settings.promptingLanguages.includes(language) && (showPrompt === undefined || showPrompt === true)) ||
|
||||
(settings.usePromptGlobal === "default off, but on when specified" && (showPrompt === true))
|
||||
) {
|
||||
elem_div.classList.add("hk-codeblock-show-prompt");
|
||||
|
||||
// using Prism.js command-line plugin
|
||||
let code_lines = elem_code.innerText.split("\n");
|
||||
if (code_lines[code_lines.length - 1].trim().length == 0) code_lines.pop(); // remove last line if empty
|
||||
|
||||
const no_prompt_line_idxs = code_lines.map((line, idx) => {
|
||||
if (line.trim().length == 0) return (idx + 1); // empty line
|
||||
if (line.startsWith("#")) return (idx + 1); // comment line
|
||||
return -1;
|
||||
}).filter((idx) => idx != -1);
|
||||
|
||||
elem_pre.classList.add("command-line");
|
||||
elem_pre.setAttribute("data-prompt", prompt);
|
||||
elem_pre.setAttribute("data-continuation-str", "\\");
|
||||
elem_pre.setAttribute("data-continuation-prompt", " ".repeat(prompt.length)); // use spaces to align continuation prompt
|
||||
elem_pre.setAttribute("data-output", no_prompt_line_idxs.join(", "));
|
||||
}
|
||||
|
||||
if(settings.useResultGlobal === "enable" && (isResult === true)) {
|
||||
elem_div.classList.add("hk-codeblock-result");
|
||||
elem_code.classList.add("hk-codeblock-result-code");
|
||||
|
||||
elem_div.setAttribute("data-result-prompt", resultPrompt);
|
||||
}
|
||||
}
|
||||
267
src/HKCodeBlockSettingTab.ts
Executable file
|
|
@ -0,0 +1,267 @@
|
|||
import { App, PluginSettingTab, Setting } from 'obsidian';
|
||||
|
||||
import HKCodeBlockPlugin from './main';
|
||||
|
||||
export const fragWithHTML = (text: string) => {
|
||||
return createFragment((frag) => {
|
||||
frag.createDiv().innerHTML = text.split("\n").map((line) => line.trim()).join("<br>");
|
||||
});
|
||||
}
|
||||
|
||||
export class HKCodeBlockSettingTab extends PluginSettingTab {
|
||||
plugin: HKCodeBlockPlugin;
|
||||
|
||||
constructor(app: App, plugin: HKCodeBlockPlugin) {
|
||||
super(app, plugin);
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
display(): void {
|
||||
const { containerEl } = this;
|
||||
containerEl.empty();
|
||||
|
||||
// title
|
||||
containerEl.createEl('h2', { text: 'Title' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Use Title')
|
||||
.setDesc(fragWithHTML(`- <b>default off, but on when specified</b> : the title will only be shown if the title statement is specified
|
||||
- <b>always off</b> : the title will never be shown`))
|
||||
.addDropdown(tc =>
|
||||
tc.addOptions({
|
||||
"default off, but on when specified": "default off, but on when specified",
|
||||
"always off": "always off",
|
||||
})
|
||||
.setValue(this.plugin.settings.useTitleGlobal)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.useTitleGlobal = value as ("default off, but on when specified" | "always off");
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// line numbers
|
||||
containerEl.createEl('h2', { text: 'Line Numbers' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Use Line Numbers')
|
||||
.setDesc(fragWithHTML(`- <b>always on</b> : line numbers will always be shown
|
||||
- <b>default on, but off when specified</b> : line numbers will always be shown, but will not be shown if linenos statement is specified as false
|
||||
- <b>default off, but on when specified</b> : line numbers will only be shown if the linenos statement is specified as true
|
||||
- <b>always off</b> : line numbers will never be shown`))
|
||||
.addDropdown(tc =>
|
||||
tc.addOptions({
|
||||
"always on": "always on",
|
||||
"default on, but off when specified": "default on, but off when specified",
|
||||
"default off, but on when specified": "default off, but on when specified",
|
||||
"always off": "always off",
|
||||
})
|
||||
.setValue(this.plugin.settings.useLinenosGlobal)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.useLinenosGlobal = value as ("always on" | "default on, but off when specified" | "default off, but on when specified" | "always off");
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Default Line Number Start')
|
||||
.setDesc('The default line number start of the code block.')
|
||||
.addText(tc =>
|
||||
tc.setValue(this.plugin.settings.defaultLinenosStart.toString())
|
||||
.onChange(async (value: string) => {
|
||||
const num = parseInt(value);
|
||||
if (isNaN(num)) return;
|
||||
|
||||
this.plugin.settings.defaultLinenosStart = num;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Show Line Number Splitter')
|
||||
.addToggle(tc =>
|
||||
tc.setValue(this.plugin.settings.showLinenosSplitter)
|
||||
.onChange(async (value: boolean) => {
|
||||
this.plugin.settings.showLinenosSplitter = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// line highlight
|
||||
containerEl.createEl('h2', { text: 'Line Highlight' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Use Line Highlight')
|
||||
.setDesc(fragWithHTML(`- <b>default off, but on when specified</b> : line highlight will only be shown if the line highlight statement is specified
|
||||
- <b>always off</b> : line highlight will never be shown`))
|
||||
.addDropdown(tc =>
|
||||
tc.addOptions({
|
||||
"default off, but on when specified": "default off, but on when specified",
|
||||
"always off": "always off",
|
||||
})
|
||||
.setValue(this.plugin.settings.useHighlightGlobal)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.useHighlightGlobal = value as ("default off, but on when specified" | "always off");
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Line Highlight Color')
|
||||
.setDesc('The background color of the highlighted line. Note that the given color will be applied with 0.2 opacity.')
|
||||
.addText(tc =>
|
||||
tc.setValue(this.plugin.settings.highlightColor)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.highlightColor = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// language
|
||||
containerEl.createEl('h2', { text: 'Language Indicator' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Use Language Indicator')
|
||||
.setDesc(fragWithHTML(`- <b>always on</b> : language indicator will always be shown
|
||||
- <b>default on, but off when specified</b> : language indicator will always be shown, but will not be shown if the language indicator statement is specified as false
|
||||
- <b>default off, but on when specified</b> : language indicator will only be shown if the language indicator statement is specified as true
|
||||
- <b>always off</b> : language indicator will never be shown`))
|
||||
.addDropdown(tc =>
|
||||
tc.addOptions({
|
||||
"always on": "always on",
|
||||
"default on, but off when specified": "default on, but off when specified",
|
||||
"default off, but on when specified": "default off, but on when specified",
|
||||
"always off": "always off",
|
||||
})
|
||||
.setValue(this.plugin.settings.useLanguageGlobal)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.useLanguageGlobal = value as ("always on" | "default on, but off when specified" | "default off, but on when specified" | "always off");
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Default Language')
|
||||
.setDesc('The default language name of the code block if the language is not specified.')
|
||||
.addText(tc =>
|
||||
tc.setValue(this.plugin.settings.defaultLanguage)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.defaultLanguage = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// copy button
|
||||
containerEl.createEl('h2', { text: 'Copy Button' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Use Copy Button')
|
||||
.setDesc(fragWithHTML(`- <b>always on</b> : copy button will always be shown
|
||||
- <b>default on, but off when specified</b> : copy button will always be shown, but will not be shown if the copy button statement is specified as false
|
||||
- <b>default off, but on when specified</b> : copy button will only be shown if the copy button statement is specified as true
|
||||
- <b>always off</b> : copy button will never be shown`))
|
||||
.addDropdown(tc =>
|
||||
tc.addOptions({
|
||||
"always on": "always on",
|
||||
"default on, but off when specified": "default on, but off when specified",
|
||||
"default off, but on when specified": "default off, but on when specified",
|
||||
"always off": "always off",
|
||||
})
|
||||
.setValue(this.plugin.settings.useCopyBtnGlobal)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.useCopyBtnGlobal = value as ("always on" | "default on, but off when specified" | "default off, but on when specified" | "always off");
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// prompt
|
||||
containerEl.createEl('h2', { text: 'Prompt' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Use Prompt')
|
||||
.setDesc(fragWithHTML(`- <b>always on</b> : prompt will always be shown
|
||||
- <b>default on, but off when specified</b> : prompt will always be shown, but will not be shown if the prompt statement is specified as false
|
||||
- <b>default off, but on when specified</b> : prompt will only be shown if the prompt statement is specified as true
|
||||
- <b>always off</b> : prompt will never be shown
|
||||
You can also specify the prompt by the statement like \`prompt:"My Prompt"\`. (default: "$")`))
|
||||
.addDropdown(tc =>
|
||||
tc.addOptions({
|
||||
"always on": "always on",
|
||||
"default on, but off when specified": "default on, but off when specified",
|
||||
"default off, but on when specified": "default off, but on when specified",
|
||||
"always off": "always off",
|
||||
})
|
||||
.setValue(this.plugin.settings.usePromptGlobal)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.usePromptGlobal = value as ("always on" | "default on, but off when specified" | "default off, but on when specified" | "always off");
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Prompting Languages')
|
||||
.setDesc('The languages that will be prompted by default. You can specify multiple languages by separating them with enters.')
|
||||
.addTextArea(tc =>
|
||||
tc.setValue(this.plugin.settings.promptingLanguages.join("\n"))
|
||||
.onChange(async (value: string) => {
|
||||
const lines = value.split("\n").filter(line => line.trim() !== "");
|
||||
this.plugin.settings.promptingLanguages = lines;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Default Prompt")
|
||||
.setDesc("The default prompt of the bash code block.")
|
||||
.addText(tc =>
|
||||
tc.setValue(this.plugin.settings.defaultPrompt)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.defaultPrompt = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// result
|
||||
containerEl.createEl('h2', { text: 'Result' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Use Result')
|
||||
.setDesc(fragWithHTML(`- <b>enable</b> : treat the code block starts like \`\`\`\`result\` as the result of the adjacent previous code block
|
||||
- <b>disable</b> : treat the code block with \`result\` as its language name as a normal code block`))
|
||||
.addDropdown(tc =>
|
||||
tc.addOptions({
|
||||
"enable": "enable",
|
||||
"disable": "disable",
|
||||
})
|
||||
.setValue(this.plugin.settings.useResultGlobal)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.useResultGlobal = value as ("enable" | "disable");
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName("Default Result Prompt")
|
||||
.setDesc("The default prompt of the result code block.")
|
||||
.addText(tc =>
|
||||
tc.setValue(this.plugin.settings.defaultResultPrompt)
|
||||
.onChange(async (value: string) => {
|
||||
this.plugin.settings.defaultResultPrompt = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
|
||||
// for developers
|
||||
containerEl.createEl('h2', { text: 'Developers' });
|
||||
|
||||
new Setting(containerEl)
|
||||
.setName('Debug Mode')
|
||||
.setDesc('Show logs in the console.')
|
||||
.addToggle(tc =>
|
||||
tc.setValue(this.plugin.settings.debugMode)
|
||||
.onChange(async (value: boolean) => {
|
||||
this.plugin.settings.debugMode = value;
|
||||
await this.plugin.saveSettings();
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
49
src/HKCodeBlockSettings.ts
Executable file
|
|
@ -0,0 +1,49 @@
|
|||
export interface HKCodeBlockSettings {
|
||||
useTitleGlobal: ("default off, but on when specified" | "always off");
|
||||
|
||||
useLinenosGlobal: ("always on" | "default on, but off when specified" | "default off, but on when specified" | "always off");
|
||||
defaultLinenosStart: number;
|
||||
showLinenosSplitter: boolean;
|
||||
|
||||
useHighlightGlobal: ("default off, but on when specified" | "always off");
|
||||
highlightColor: string;
|
||||
|
||||
useLanguageGlobal: ("always on" | "default on, but off when specified" | "default off, but on when specified" | "always off");
|
||||
defaultLanguage: string;
|
||||
|
||||
useCopyBtnGlobal: ("always on" | "default on, but off when specified" | "default off, but on when specified" | "always off");
|
||||
|
||||
usePromptGlobal: ("always on" | "default on, but off when specified" | "default off, but on when specified" | "always off");
|
||||
defaultPrompt: string;
|
||||
|
||||
useResultGlobal: ("enable" | "disable");
|
||||
promptingLanguages: string[];
|
||||
defaultResultPrompt: string;
|
||||
|
||||
debugMode: boolean;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: HKCodeBlockSettings = {
|
||||
useTitleGlobal: "default off, but on when specified",
|
||||
|
||||
useLinenosGlobal: "default off, but on when specified",
|
||||
defaultLinenosStart: 1,
|
||||
showLinenosSplitter: true,
|
||||
|
||||
useHighlightGlobal: "default off, but on when specified",
|
||||
highlightColor: "#ff0000",
|
||||
|
||||
useLanguageGlobal: "default on, but off when specified",
|
||||
defaultLanguage: "plain text",
|
||||
|
||||
useCopyBtnGlobal: "default on, but off when specified",
|
||||
|
||||
usePromptGlobal: "default on, but off when specified",
|
||||
promptingLanguages: ["bash"],
|
||||
defaultPrompt: "$",
|
||||
|
||||
useResultGlobal: "enable",
|
||||
defaultResultPrompt: "Result",
|
||||
|
||||
debugMode: false,
|
||||
}
|
||||
48
src/main.ts
Executable file
|
|
@ -0,0 +1,48 @@
|
|||
import { Plugin, loadPrism } from 'obsidian';
|
||||
|
||||
import type { HKCodeBlockSettings } from './HKCodeBlockSettings';
|
||||
import { DEFAULT_SETTINGS } from './HKCodeBlockSettings';
|
||||
import { HKCodeBlockSettingTab } from './HKCodeBlockSettingTab';
|
||||
import { HKCodeBlockProcessor } from './HKCodeBlockProcessor';
|
||||
|
||||
import { PrismPlugin_CommandLine } from './prism-plugins/prism-command-line';
|
||||
|
||||
export default class HKCodeBlockPlugin extends Plugin {
|
||||
settings: HKCodeBlockSettings;
|
||||
|
||||
async onload() {
|
||||
await this.loadSettings();
|
||||
if (this.settings.debugMode) {
|
||||
console.log("loading HKCodeBlock plugin");
|
||||
}
|
||||
|
||||
const Prism = await loadPrism();
|
||||
|
||||
// activate Prism.js plugins
|
||||
PrismPlugin_CommandLine(Prism);
|
||||
|
||||
// register settings tab
|
||||
this.addSettingTab(new HKCodeBlockSettingTab(this.app, this));
|
||||
|
||||
// register markdown post processor
|
||||
const processor = this.registerMarkdownPostProcessor((el, ctx) => {
|
||||
HKCodeBlockProcessor(el, ctx, this);
|
||||
});
|
||||
|
||||
processor.sortOrder = 100; // run after the default code block processor
|
||||
}
|
||||
|
||||
onunload() {
|
||||
if (this.settings.debugMode) {
|
||||
console.log("unloading HKCodeBlock plugin");
|
||||
}
|
||||
}
|
||||
|
||||
async loadSettings() {
|
||||
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
|
||||
}
|
||||
|
||||
async saveSettings() {
|
||||
await this.saveData(this.settings);
|
||||
}
|
||||
}
|
||||
243
src/prism-plugins/prism-command-line.js
Executable file
|
|
@ -0,0 +1,243 @@
|
|||
export function PrismPlugin_CommandLine(Prism) {
|
||||
|
||||
if (typeof Prism === 'undefined' || typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
var CLASS_PATTERN = /(?:^|\s)command-line(?:\s|$)/;
|
||||
var PROMPT_CLASS = 'command-line-prompt';
|
||||
|
||||
/** @type {(str: string, prefix: string) => boolean} */
|
||||
var startsWith = ''.startsWith
|
||||
? function (s, p) { return s.startsWith(p); }
|
||||
: function (s, p) { return s.indexOf(p) === 0; };
|
||||
|
||||
// Support for IE11 that has no endsWith()
|
||||
/** @type {(str: string, suffix: string) => boolean} */
|
||||
var endsWith = ''.endsWith
|
||||
? function (str, suffix) {
|
||||
return str.endsWith(suffix);
|
||||
}
|
||||
: function (str, suffix) {
|
||||
var len = str.length;
|
||||
return str.substring(len - suffix.length, len) === suffix;
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns whether the given hook environment has a command line info object.
|
||||
*
|
||||
* @param {any} env
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasCommandLineInfo(env) {
|
||||
var vars = env.vars = env.vars || {};
|
||||
return 'command-line' in vars;
|
||||
}
|
||||
/**
|
||||
* Returns the command line info object from the given hook environment.
|
||||
*
|
||||
* @param {any} env
|
||||
* @returns {CommandLineInfo}
|
||||
*
|
||||
* @typedef CommandLineInfo
|
||||
* @property {boolean} [complete]
|
||||
* @property {number} [numberOfLines]
|
||||
* @property {string[]} [outputLines]
|
||||
*/
|
||||
function getCommandLineInfo(env) {
|
||||
var vars = env.vars = env.vars || {};
|
||||
return vars['command-line'] = vars['command-line'] || {};
|
||||
}
|
||||
|
||||
|
||||
Prism.hooks.add('before-highlight', function (env) {
|
||||
var commandLine = getCommandLineInfo(env);
|
||||
|
||||
if (commandLine.complete || !env.code) {
|
||||
commandLine.complete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// Works only for <code> wrapped inside <pre> (not inline).
|
||||
var pre = env.element.parentElement;
|
||||
if (!pre || !/pre/i.test(pre.nodeName) || // Abort only if neither the <pre> nor the <code> have the class
|
||||
(!CLASS_PATTERN.test(pre.className) && !CLASS_PATTERN.test(env.element.className))) {
|
||||
commandLine.complete = true;
|
||||
return;
|
||||
}
|
||||
|
||||
// The element might be highlighted multiple times, so we just remove the previous prompt
|
||||
var existingPrompt = env.element.querySelector('.' + PROMPT_CLASS);
|
||||
if (existingPrompt) {
|
||||
existingPrompt.remove();
|
||||
}
|
||||
|
||||
var codeLines = env.code.split('\n');
|
||||
|
||||
if(codeLines[codeLines.length - 1].trim().length === 0) { // do not count the last line if it is empty
|
||||
commandLine.numberOfLines = codeLines.length - 1;
|
||||
} else {
|
||||
commandLine.numberOfLines = codeLines.length;
|
||||
}
|
||||
/** @type {string[]} */
|
||||
var outputLines = commandLine.outputLines = [];
|
||||
|
||||
var outputSections = pre.getAttribute('data-output');
|
||||
var outputFilter = pre.getAttribute('data-filter-output');
|
||||
if (outputSections !== null) { // The user specified the output lines. -- cwells
|
||||
outputSections.split(',').forEach(function (section) {
|
||||
var range = section.split('-');
|
||||
var outputStart = parseInt(range[0], 10);
|
||||
var outputEnd = range.length === 2 ? parseInt(range[1], 10) : outputStart;
|
||||
|
||||
if (!isNaN(outputStart) && !isNaN(outputEnd)) {
|
||||
if (outputStart < 1) {
|
||||
outputStart = 1;
|
||||
}
|
||||
if (outputEnd > codeLines.length) {
|
||||
outputEnd = codeLines.length;
|
||||
}
|
||||
// Convert start and end to 0-based to simplify the arrays. -- cwells
|
||||
outputStart--;
|
||||
outputEnd--;
|
||||
// Save the output line in an array and clear it in the code so it's not highlighted. -- cwells
|
||||
for (var j = outputStart; j <= outputEnd; j++) {
|
||||
outputLines[j] = codeLines[j];
|
||||
codeLines[j] = '';
|
||||
}
|
||||
}
|
||||
});
|
||||
} else if (outputFilter) { // Treat lines beginning with this string as output. -- cwells
|
||||
for (var i = 0; i < codeLines.length; i++) {
|
||||
if (startsWith(codeLines[i], outputFilter)) { // This line is output. -- cwells
|
||||
outputLines[i] = codeLines[i].slice(outputFilter.length);
|
||||
codeLines[i] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var continuationLineIndicies = commandLine.continuationLineIndicies = new Set();
|
||||
var lineContinuationStr = pre.getAttribute('data-continuation-str');
|
||||
var continuationFilter = pre.getAttribute('data-filter-continuation');
|
||||
|
||||
// Identify code lines where the command has continued onto subsequent
|
||||
// lines and thus need a different prompt. Need to do this after the output
|
||||
// lines have been removed to ensure we don't pick up a continuation string
|
||||
// in an output line.
|
||||
for (var j = 0; j < codeLines.length; j++) {
|
||||
var line = codeLines[j];
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Record the next line as a continuation if this one ends in a continuation str.
|
||||
if (lineContinuationStr && endsWith(line, lineContinuationStr)) {
|
||||
continuationLineIndicies.add(j + 1);
|
||||
}
|
||||
// Record this line as a continuation if marked with a continuation prefix
|
||||
// (that we will remove).
|
||||
if (j > 0 && continuationFilter && startsWith(line, continuationFilter)) {
|
||||
codeLines[j] = line.slice(continuationFilter.length);
|
||||
continuationLineIndicies.add(j);
|
||||
}
|
||||
}
|
||||
|
||||
env.code = codeLines.join('\n');
|
||||
});
|
||||
|
||||
Prism.hooks.add('before-insert', function (env) {
|
||||
var commandLine = getCommandLineInfo(env);
|
||||
|
||||
if (commandLine.complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Reinsert the output lines into the highlighted code. -- cwells
|
||||
var codeLines = env.highlightedCode.split('\n');
|
||||
var outputLines = commandLine.outputLines || [];
|
||||
for (var i = 0, l = codeLines.length; i < l; i++) {
|
||||
// Add spans to allow distinction of input/output text for styling
|
||||
if (outputLines.hasOwnProperty(i)) {
|
||||
// outputLines were removed from codeLines so missed out on escaping
|
||||
// of markup so do it here.
|
||||
codeLines[i] = '<span class="token output">'
|
||||
+ Prism.util.encode(outputLines[i]) + '</span>';
|
||||
} else {
|
||||
codeLines[i] = '<span class="token command">'
|
||||
+ codeLines[i] + '</span>';
|
||||
}
|
||||
}
|
||||
env.highlightedCode = codeLines.join('\n');
|
||||
});
|
||||
|
||||
Prism.hooks.add('complete', function (env) {
|
||||
if (!hasCommandLineInfo(env)) {
|
||||
// the previous hooks never ran
|
||||
return;
|
||||
}
|
||||
|
||||
var commandLine = getCommandLineInfo(env);
|
||||
|
||||
if (commandLine.complete) {
|
||||
return;
|
||||
}
|
||||
|
||||
var pre = env.element.parentElement;
|
||||
if (CLASS_PATTERN.test(env.element.className)) { // Remove the class "command-line" from the <code>
|
||||
env.element.className = env.element.className.replace(CLASS_PATTERN, ' ');
|
||||
}
|
||||
if (!CLASS_PATTERN.test(pre.className)) { // Add the class "command-line" to the <pre>
|
||||
pre.className += ' command-line';
|
||||
}
|
||||
|
||||
function getAttribute(key, defaultValue) {
|
||||
return (pre.getAttribute(key) || defaultValue).replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// Create the "rows" that will become the command-line prompts. -- cwells
|
||||
var promptLines = '';
|
||||
var rowCount = commandLine.numberOfLines || 0;
|
||||
var promptText = getAttribute('data-prompt', '');
|
||||
var promptLine;
|
||||
if (promptText !== '') {
|
||||
promptLine = '<span data-prompt="' + promptText + '"></span>';
|
||||
} else {
|
||||
var user = getAttribute('data-user', 'user');
|
||||
var host = getAttribute('data-host', 'localhost');
|
||||
promptLine = '<span data-user="' + user + '" data-host="' + host + '"></span>';
|
||||
}
|
||||
|
||||
var continuationLineIndicies = commandLine.continuationLineIndicies || new Set();
|
||||
var continuationPromptText = getAttribute('data-continuation-prompt', '>');
|
||||
var continuationPromptLine = '<span data-continuation-prompt="' + continuationPromptText + '"></span>';
|
||||
|
||||
// Assemble all the appropriate prompt/continuation lines
|
||||
for (var j = 0; j < rowCount; j++) {
|
||||
if (continuationLineIndicies.has(j)) {
|
||||
promptLines += continuationPromptLine;
|
||||
} else {
|
||||
promptLines += promptLine;
|
||||
}
|
||||
}
|
||||
|
||||
// Create the wrapper element. -- cwells
|
||||
var prompt = document.createElement('span');
|
||||
prompt.className = PROMPT_CLASS;
|
||||
prompt.innerHTML = promptLines;
|
||||
|
||||
// Remove the prompt from the output lines. -- cwells
|
||||
var outputLines = commandLine.outputLines || [];
|
||||
for (var i = 0, l = outputLines.length; i < l; i++) {
|
||||
if (outputLines.hasOwnProperty(i)) {
|
||||
var node = prompt.children[i];
|
||||
node.removeAttribute('data-user');
|
||||
node.removeAttribute('data-host');
|
||||
node.removeAttribute('data-prompt');
|
||||
}
|
||||
}
|
||||
|
||||
env.element.insertBefore(prompt, env.element.firstChild);
|
||||
commandLine.complete = true;
|
||||
});
|
||||
|
||||
}
|
||||
346
src/prism-plugins/prism-line-highlight.js
Executable file
|
|
@ -0,0 +1,346 @@
|
|||
export function PrismPlugin_LineHighlight(Prism) {
|
||||
|
||||
if (typeof Prism === 'undefined' || typeof document === 'undefined' || !document.querySelector) {
|
||||
return;
|
||||
}
|
||||
|
||||
var LINE_NUMBERS_CLASS = 'line-numbers';
|
||||
var LINKABLE_LINE_NUMBERS_CLASS = 'linkable-line-numbers';
|
||||
var NEW_LINE_EXP = /\n(?!$)/g;
|
||||
|
||||
/**
|
||||
* @param {string} selector
|
||||
* @param {ParentNode} [container]
|
||||
* @returns {HTMLElement[]}
|
||||
*/
|
||||
function $$(selector, container) {
|
||||
return Array.prototype.slice.call((container || document).querySelectorAll(selector));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given element has the given class.
|
||||
*
|
||||
* @param {Element} element
|
||||
* @param {string} className
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function hasClass(element, className) {
|
||||
return element.classList.contains(className);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the given function.
|
||||
*
|
||||
* @param {() => any} func
|
||||
* @returns {void}
|
||||
*/
|
||||
function callFunction(func) {
|
||||
func();
|
||||
}
|
||||
|
||||
// Some browsers round the line-height, others don't.
|
||||
// We need to test for it to position the elements properly.
|
||||
var isLineHeightRounded = (function () {
|
||||
var res;
|
||||
return function () {
|
||||
if (typeof res === 'undefined') {
|
||||
var d = document.createElement('div');
|
||||
d.style.fontSize = '13px';
|
||||
d.style.lineHeight = '1.5';
|
||||
d.style.padding = '0';
|
||||
d.style.border = '0';
|
||||
d.innerHTML = ' <br /> ';
|
||||
document.body.appendChild(d);
|
||||
// Browsers that round the line-height should have offsetHeight === 38
|
||||
// The others should have 39.
|
||||
res = d.offsetHeight === 38;
|
||||
document.body.removeChild(d);
|
||||
}
|
||||
return res;
|
||||
};
|
||||
}());
|
||||
|
||||
/**
|
||||
* Returns the top offset of the content box of the given parent and the content box of one of its children.
|
||||
*
|
||||
* @param {HTMLElement} parent
|
||||
* @param {HTMLElement} child
|
||||
*/
|
||||
function getContentBoxTopOffset(parent, child) {
|
||||
var parentStyle = getComputedStyle(parent);
|
||||
var childStyle = getComputedStyle(child);
|
||||
|
||||
/**
|
||||
* Returns the numeric value of the given pixel value.
|
||||
*
|
||||
* @param {string} px
|
||||
*/
|
||||
function pxToNumber(px) {
|
||||
return +px.substr(0, px.length - 2);
|
||||
}
|
||||
|
||||
return child.offsetTop
|
||||
+ pxToNumber(childStyle.borderTopWidth)
|
||||
+ pxToNumber(childStyle.paddingTop)
|
||||
- pxToNumber(parentStyle.paddingTop);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the Line Highlight plugin is active for the given element.
|
||||
*
|
||||
* If this function returns `false`, do not call `highlightLines` for the given element.
|
||||
*
|
||||
* @param {HTMLElement | null | undefined} pre
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function isActiveFor(pre) {
|
||||
if (!pre || !/pre/i.test(pre.nodeName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (pre.hasAttribute('data-line')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (pre.id && Prism.util.isActive(pre, LINKABLE_LINE_NUMBERS_CLASS)) {
|
||||
// Technically, the line numbers plugin is also necessary but this plugin doesn't control the classes of
|
||||
// the line numbers plugin, so we can't assume that they are present.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
var scrollIntoView = true;
|
||||
|
||||
Prism.plugins.lineHighlight = {
|
||||
/**
|
||||
* Highlights the lines of the given pre.
|
||||
*
|
||||
* This function is split into a DOM measuring and mutate phase to improve performance.
|
||||
* The returned function mutates the DOM when called.
|
||||
*
|
||||
* @param {HTMLElement} pre
|
||||
* @param {string | null} [lines]
|
||||
* @param {string} [classes='']
|
||||
* @returns {() => void}
|
||||
*/
|
||||
highlightLines: function highlightLines(pre, lines, classes) {
|
||||
lines = typeof lines === 'string' ? lines : (pre.getAttribute('data-line') || '');
|
||||
|
||||
var ranges = lines.replace(/\s+/g, '').split(',').filter(Boolean);
|
||||
var offset = +pre.getAttribute('data-line-offset') || 0;
|
||||
|
||||
var parseMethod = isLineHeightRounded() ? parseInt : parseFloat;
|
||||
var lineHeight = parseMethod(getComputedStyle(pre).lineHeight);
|
||||
var hasLineNumbers = Prism.util.isActive(pre, LINE_NUMBERS_CLASS);
|
||||
var codeElement = pre.querySelector('code');
|
||||
var parentElement = hasLineNumbers ? pre : codeElement || pre;
|
||||
var mutateActions = /** @type {(() => void)[]} */ ([]);
|
||||
var lineBreakMatch = codeElement.textContent.match(NEW_LINE_EXP);
|
||||
var numberOfLines = lineBreakMatch ? lineBreakMatch.length + 1 : 1;
|
||||
/**
|
||||
* The top offset between the content box of the <code> element and the content box of the parent element of
|
||||
* the line highlight element (either `<pre>` or `<code>`).
|
||||
*
|
||||
* This offset might not be zero for some themes where the <code> element has a top margin. Some plugins
|
||||
* (or users) might also add element above the <code> element. Because the line highlight is aligned relative
|
||||
* to the <pre> element, we have to take this into account.
|
||||
*
|
||||
* This offset will be 0 if the parent element of the line highlight element is the `<code>` element.
|
||||
*/
|
||||
var codePreOffset = !codeElement || parentElement == codeElement ? 0 : getContentBoxTopOffset(pre, codeElement);
|
||||
|
||||
ranges.forEach(function (currentRange) {
|
||||
var range = currentRange.split('-');
|
||||
|
||||
var start = +range[0];
|
||||
var end = +range[1] || start;
|
||||
end = Math.min(numberOfLines + offset, end);
|
||||
|
||||
if (end < start) {
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
var line = pre.querySelector('.line-highlight[data-range="' + currentRange + '"]') || document.createElement('div');
|
||||
|
||||
mutateActions.push(function () {
|
||||
line.setAttribute('aria-hidden', 'true');
|
||||
line.setAttribute('data-range', currentRange);
|
||||
line.className = (classes || '') + ' line-highlight';
|
||||
});
|
||||
|
||||
// if the line-numbers plugin is enabled, then there is no reason for this plugin to display the line numbers
|
||||
if (hasLineNumbers && Prism.plugins.lineNumbers) {
|
||||
var startNode = Prism.plugins.lineNumbers.getLine(pre, start);
|
||||
var endNode = Prism.plugins.lineNumbers.getLine(pre, end);
|
||||
|
||||
if (startNode) {
|
||||
var top = startNode.offsetTop + codePreOffset + 'px';
|
||||
mutateActions.push(function () {
|
||||
line.style.top = top;
|
||||
});
|
||||
}
|
||||
|
||||
if (endNode) {
|
||||
var height = (endNode.offsetTop - startNode.offsetTop) + endNode.offsetHeight + 'px';
|
||||
mutateActions.push(function () {
|
||||
line.style.height = height;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
mutateActions.push(function () {
|
||||
line.setAttribute('data-start', String(start));
|
||||
|
||||
if (end > start) {
|
||||
line.setAttribute('data-end', String(end));
|
||||
}
|
||||
|
||||
line.style.top = (start - offset - 1) * lineHeight + codePreOffset + 'px';
|
||||
|
||||
line.textContent = new Array(end - start + 2).join(' \n');
|
||||
});
|
||||
}
|
||||
|
||||
mutateActions.push(function () {
|
||||
line.style.width = pre.scrollWidth + 'px';
|
||||
});
|
||||
|
||||
mutateActions.push(function () {
|
||||
// allow this to play nicely with the line-numbers plugin
|
||||
// need to attack to pre as when line-numbers is enabled, the code tag is relatively which screws up the positioning
|
||||
parentElement.appendChild(line);
|
||||
});
|
||||
});
|
||||
|
||||
var id = pre.id;
|
||||
if (hasLineNumbers && Prism.util.isActive(pre, LINKABLE_LINE_NUMBERS_CLASS) && id) {
|
||||
// This implements linkable line numbers. Linkable line numbers use Line Highlight to create a link to a
|
||||
// specific line. For this to work, the pre element has to:
|
||||
// 1) have line numbers,
|
||||
// 2) have the `linkable-line-numbers` class or an ascendant that has that class, and
|
||||
// 3) have an id.
|
||||
|
||||
if (!hasClass(pre, LINKABLE_LINE_NUMBERS_CLASS)) {
|
||||
// add class to pre
|
||||
mutateActions.push(function () {
|
||||
pre.classList.add(LINKABLE_LINE_NUMBERS_CLASS);
|
||||
});
|
||||
}
|
||||
|
||||
var start = parseInt(pre.getAttribute('data-start') || '1');
|
||||
|
||||
// iterate all line number spans
|
||||
$$('.line-numbers-rows > span', pre).forEach(function (lineSpan, i) {
|
||||
var lineNumber = i + start;
|
||||
lineSpan.onclick = function () {
|
||||
var hash = id + '.' + lineNumber;
|
||||
|
||||
// this will prevent scrolling since the span is obviously in view
|
||||
scrollIntoView = false;
|
||||
location.hash = hash;
|
||||
setTimeout(function () {
|
||||
scrollIntoView = true;
|
||||
}, 1);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
return function () {
|
||||
mutateActions.forEach(callFunction);
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
function applyHash() {
|
||||
var hash = location.hash.slice(1);
|
||||
|
||||
// Remove pre-existing temporary lines
|
||||
$$('.temporary.line-highlight').forEach(function (line) {
|
||||
line.parentNode.removeChild(line);
|
||||
});
|
||||
|
||||
var range = (hash.match(/\.([\d,-]+)$/) || [, ''])[1];
|
||||
|
||||
if (!range || document.getElementById(hash)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var id = hash.slice(0, hash.lastIndexOf('.'));
|
||||
var pre = document.getElementById(id);
|
||||
|
||||
if (!pre) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!pre.hasAttribute('data-line')) {
|
||||
pre.setAttribute('data-line', '');
|
||||
}
|
||||
|
||||
var mutateDom = Prism.plugins.lineHighlight.highlightLines(pre, range, 'temporary ');
|
||||
mutateDom();
|
||||
|
||||
if (scrollIntoView) {
|
||||
document.querySelector('.temporary.line-highlight').scrollIntoView();
|
||||
}
|
||||
}
|
||||
|
||||
var fakeTimer = 0; // Hack to limit the number of times applyHash() runs
|
||||
|
||||
Prism.hooks.add('before-sanity-check', function (env) {
|
||||
var pre = env.element.parentElement;
|
||||
if (!isActiveFor(pre)) {
|
||||
return;
|
||||
}
|
||||
|
||||
/*
|
||||
* Cleanup for other plugins (e.g. autoloader).
|
||||
*
|
||||
* Sometimes <code> blocks are highlighted multiple times. It is necessary
|
||||
* to cleanup any left-over tags, because the whitespace inside of the <div>
|
||||
* tags change the content of the <code> tag.
|
||||
*/
|
||||
var num = 0;
|
||||
$$('.line-highlight', pre).forEach(function (line) {
|
||||
num += line.textContent.length;
|
||||
line.parentNode.removeChild(line);
|
||||
});
|
||||
// Remove extra whitespace
|
||||
if (num && /^(?: \n)+$/.test(env.code.slice(-num))) {
|
||||
env.code = env.code.slice(0, -num);
|
||||
}
|
||||
});
|
||||
|
||||
Prism.hooks.add('complete', function completeHook(env) {
|
||||
var pre = env.element.parentElement;
|
||||
if (!isActiveFor(pre)) {
|
||||
return;
|
||||
}
|
||||
|
||||
clearTimeout(fakeTimer);
|
||||
|
||||
var hasLineNumbers = Prism.plugins.lineNumbers;
|
||||
var isLineNumbersLoaded = env.plugins && env.plugins.lineNumbers;
|
||||
|
||||
if (hasClass(pre, LINE_NUMBERS_CLASS) && hasLineNumbers && !isLineNumbersLoaded) {
|
||||
Prism.hooks.add('line-numbers', completeHook);
|
||||
} else {
|
||||
var mutateDom = Prism.plugins.lineHighlight.highlightLines(pre);
|
||||
mutateDom();
|
||||
fakeTimer = setTimeout(applyHash, 1);
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener('hashchange', applyHash);
|
||||
window.addEventListener('resize', function () {
|
||||
var actions = $$('pre')
|
||||
.filter(isActiveFor)
|
||||
.map(function (pre) {
|
||||
return Prism.plugins.lineHighlight.highlightLines(pre);
|
||||
});
|
||||
actions.forEach(callFunction);
|
||||
});
|
||||
|
||||
}
|
||||
252
src/prism-plugins/prism-line-numbers.js
Executable file
|
|
@ -0,0 +1,252 @@
|
|||
export function PrismPlugin_LineNumbers(Prism) {
|
||||
|
||||
if (typeof Prism === 'undefined' || typeof document === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* Plugin name which is used as a class name for <pre> which is activating the plugin
|
||||
*
|
||||
* @type {string}
|
||||
*/
|
||||
var PLUGIN_NAME = 'line-numbers';
|
||||
|
||||
/**
|
||||
* Regular expression used for determining line breaks
|
||||
*
|
||||
* @type {RegExp}
|
||||
*/
|
||||
var NEW_LINE_EXP = /\n(?!$)/g;
|
||||
|
||||
|
||||
/**
|
||||
* Global exports
|
||||
*/
|
||||
var config = Prism.plugins.lineNumbers = {
|
||||
/**
|
||||
* Get node for provided line number
|
||||
*
|
||||
* @param {Element} element pre element
|
||||
* @param {number} number line number
|
||||
* @returns {Element|undefined}
|
||||
*/
|
||||
getLine: function (element, number) {
|
||||
if (element.tagName !== 'PRE' || !element.classList.contains(PLUGIN_NAME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var lineNumberRows = element.querySelector('.line-numbers-rows');
|
||||
if (!lineNumberRows) {
|
||||
return;
|
||||
}
|
||||
var lineNumberStart = parseInt(element.getAttribute('data-start'), 10) || 1;
|
||||
var lineNumberEnd = lineNumberStart + (lineNumberRows.children.length - 1);
|
||||
|
||||
if (number < lineNumberStart) {
|
||||
number = lineNumberStart;
|
||||
}
|
||||
if (number > lineNumberEnd) {
|
||||
number = lineNumberEnd;
|
||||
}
|
||||
|
||||
var lineIndex = number - lineNumberStart;
|
||||
|
||||
return lineNumberRows.children[lineIndex];
|
||||
},
|
||||
|
||||
/**
|
||||
* Resizes the line numbers of the given element.
|
||||
*
|
||||
* This function will not add line numbers. It will only resize existing ones.
|
||||
*
|
||||
* @param {HTMLElement} element A `<pre>` element with line numbers.
|
||||
* @returns {void}
|
||||
*/
|
||||
resize: function (element) {
|
||||
resizeElements([element]);
|
||||
},
|
||||
|
||||
/**
|
||||
* Whether the plugin can assume that the units font sizes and margins are not depended on the size of
|
||||
* the current viewport.
|
||||
*
|
||||
* Setting this to `true` will allow the plugin to do certain optimizations for better performance.
|
||||
*
|
||||
* Set this to `false` if you use any of the following CSS units: `vh`, `vw`, `vmin`, `vmax`.
|
||||
*
|
||||
* @type {boolean}
|
||||
*/
|
||||
assumeViewportIndependence: true
|
||||
};
|
||||
|
||||
/**
|
||||
* Resizes the given elements.
|
||||
*
|
||||
* @param {HTMLElement[]} elements
|
||||
*/
|
||||
function resizeElements(elements) {
|
||||
elements = elements.filter(function (e) {
|
||||
var codeStyles = getStyles(e);
|
||||
var whiteSpace = codeStyles['white-space'];
|
||||
return whiteSpace === 'pre-wrap' || whiteSpace === 'pre-line';
|
||||
});
|
||||
|
||||
if (elements.length == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var infos = elements.map(function (element) {
|
||||
var codeElement = element.querySelector('code');
|
||||
var lineNumbersWrapper = element.querySelector('.line-numbers-rows');
|
||||
if (!codeElement || !lineNumbersWrapper) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/** @type {HTMLElement} */
|
||||
var lineNumberSizer = element.querySelector('.line-numbers-sizer');
|
||||
var codeLines = codeElement.textContent.split(NEW_LINE_EXP);
|
||||
|
||||
if (!lineNumberSizer) {
|
||||
lineNumberSizer = document.createElement('span');
|
||||
lineNumberSizer.className = 'line-numbers-sizer';
|
||||
|
||||
codeElement.appendChild(lineNumberSizer);
|
||||
}
|
||||
|
||||
lineNumberSizer.innerHTML = '0';
|
||||
lineNumberSizer.style.display = 'block';
|
||||
|
||||
var oneLinerHeight = lineNumberSizer.getBoundingClientRect().height;
|
||||
lineNumberSizer.innerHTML = '';
|
||||
|
||||
return {
|
||||
element: element,
|
||||
lines: codeLines,
|
||||
lineHeights: [],
|
||||
oneLinerHeight: oneLinerHeight,
|
||||
sizer: lineNumberSizer,
|
||||
};
|
||||
}).filter(Boolean);
|
||||
|
||||
infos.forEach(function (info) {
|
||||
var lineNumberSizer = info.sizer;
|
||||
var lines = info.lines;
|
||||
var lineHeights = info.lineHeights;
|
||||
var oneLinerHeight = info.oneLinerHeight;
|
||||
|
||||
lineHeights[lines.length - 1] = undefined;
|
||||
lines.forEach(function (line, index) {
|
||||
if (line && line.length > 1) {
|
||||
var e = lineNumberSizer.appendChild(document.createElement('span'));
|
||||
e.style.display = 'block';
|
||||
e.textContent = line;
|
||||
} else {
|
||||
lineHeights[index] = oneLinerHeight;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
infos.forEach(function (info) {
|
||||
var lineNumberSizer = info.sizer;
|
||||
var lineHeights = info.lineHeights;
|
||||
|
||||
var childIndex = 0;
|
||||
for (var i = 0; i < lineHeights.length; i++) {
|
||||
if (lineHeights[i] === undefined) {
|
||||
lineHeights[i] = lineNumberSizer.children[childIndex++].getBoundingClientRect().height;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
infos.forEach(function (info) {
|
||||
var lineNumberSizer = info.sizer;
|
||||
var wrapper = info.element.querySelector('.line-numbers-rows');
|
||||
|
||||
lineNumberSizer.style.display = 'none';
|
||||
lineNumberSizer.innerHTML = '';
|
||||
|
||||
info.lineHeights.forEach(function (height, lineNumber) {
|
||||
wrapper.children[lineNumber].style.height = height + 'px';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns style declarations for the element
|
||||
*
|
||||
* @param {Element} element
|
||||
*/
|
||||
function getStyles(element) {
|
||||
if (!element) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return window.getComputedStyle ? getComputedStyle(element) : (element.currentStyle || null);
|
||||
}
|
||||
|
||||
var lastWidth = undefined;
|
||||
window.addEventListener('resize', function () {
|
||||
if (config.assumeViewportIndependence && lastWidth === window.innerWidth) {
|
||||
return;
|
||||
}
|
||||
lastWidth = window.innerWidth;
|
||||
|
||||
resizeElements(Array.prototype.slice.call(document.querySelectorAll('pre.' + PLUGIN_NAME)));
|
||||
});
|
||||
|
||||
Prism.hooks.add('complete', function (env) {
|
||||
if (!env.code) {
|
||||
return;
|
||||
}
|
||||
|
||||
var code = /** @type {Element} */ (env.element);
|
||||
var pre = /** @type {HTMLElement} */ (code.parentNode);
|
||||
|
||||
// works only for <code> wrapped inside <pre> (not inline)
|
||||
if (!pre || !/pre/i.test(pre.nodeName)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Abort if line numbers already exists
|
||||
if (code.querySelector('.line-numbers-rows')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// only add line numbers if <code> or one of its ancestors has the `line-numbers` class
|
||||
if (!Prism.util.isActive(code, PLUGIN_NAME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove the class 'line-numbers' from the <code>
|
||||
code.classList.remove(PLUGIN_NAME);
|
||||
// Add the class 'line-numbers' to the <pre>
|
||||
pre.classList.add(PLUGIN_NAME);
|
||||
|
||||
var match = env.code.match(NEW_LINE_EXP);
|
||||
var linesNum = match ? match.length + 1 : 1;
|
||||
var lineNumbersWrapper;
|
||||
|
||||
var lines = new Array(linesNum + 1).join('<span></span>');
|
||||
|
||||
lineNumbersWrapper = document.createElement('span');
|
||||
lineNumbersWrapper.setAttribute('aria-hidden', 'true');
|
||||
lineNumbersWrapper.className = 'line-numbers-rows';
|
||||
lineNumbersWrapper.innerHTML = lines;
|
||||
|
||||
if (pre.hasAttribute('data-start')) {
|
||||
pre.style.counterReset = 'linenumber ' + (parseInt(pre.getAttribute('data-start'), 10) - 1);
|
||||
}
|
||||
|
||||
env.element.appendChild(lineNumbersWrapper);
|
||||
|
||||
resizeElements([pre]);
|
||||
|
||||
Prism.hooks.run('line-numbers', env);
|
||||
});
|
||||
|
||||
Prism.hooks.add('line-numbers', function (env) {
|
||||
env.plugins = env.plugins || {};
|
||||
env.plugins.lineNumbers = true;
|
||||
});
|
||||
|
||||
}
|
||||
190
src/styles.scss
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
.hk-codeblock {
|
||||
& {
|
||||
// variables
|
||||
--hk-codeblock-margin: 1em;
|
||||
--hk-codeblock-horizontal-padding: 16px;
|
||||
--hk-codeblock-vertical-padding: 8px;
|
||||
--hk-codeblock-background-color: var(--code-background);
|
||||
--hk-codeblock-border-color: var(--divider-color); // applied to title, linenos, and result
|
||||
--hk-codeblock-title-text-color: var(--text-normal);
|
||||
--hk-codeblock-linenos-text-color: var(--code-normal);
|
||||
--hk-codeblock-language-text-color: var(--text-muted); // same with copy button
|
||||
--hk-codeblock-language-text-size: var(--font-ui-smaller); // same with copy button
|
||||
--hk-codeblock-language-text-font: var(--font-interface); // same with copy button
|
||||
--hk-codeblock-prompt-text-color: var(--code-normal);
|
||||
--hk-codeblock-result-text-color: var(--text-normal);
|
||||
}
|
||||
|
||||
position: relative;
|
||||
background-color: var(--hk-codeblock-background-color);
|
||||
margin-bottom: var(--hk-codeblock-margin);
|
||||
|
||||
pre {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin: 0;
|
||||
padding-top: var(--hk-codeblock-vertical-padding);
|
||||
padding-bottom: var(--hk-codeblock-vertical-padding);
|
||||
padding-left: var(--hk-codeblock-horizontal-padding);
|
||||
padding-right: var(--hk-codeblock-horizontal-padding);
|
||||
background-color: var(--hk-codeblock-background-color);
|
||||
|
||||
code.hk-codeblock-code {
|
||||
white-space: pre;
|
||||
overflow-x: auto;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* title */
|
||||
.hk-codeblock.hk-codeblock-show-title {
|
||||
.hk-codeblock-title {
|
||||
padding-left: var(--hk-codeblock-horizontal-padding);
|
||||
padding-right: var(--hk-codeblock-horizontal-padding);
|
||||
padding-top: var(--hk-codeblock-vertical-padding);
|
||||
padding-bottom: var(--hk-codeblock-vertical-padding);
|
||||
background-color: var(--hk-codeblock-background-color);
|
||||
border-bottom: 1px solid var(--hk-codeblock-border-color);
|
||||
color: var(--hk-codeblock-title-text-color);
|
||||
}
|
||||
|
||||
.hk-codeblock-title~pre {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* linenos */
|
||||
.hk-codeblock.hk-codeblock-show-linenos {
|
||||
pre {
|
||||
code.hk-codeblock-linenos {
|
||||
flex-shrink: 0;
|
||||
flex-grow: 0;
|
||||
text-align: right;
|
||||
padding-right: 1em;
|
||||
opacity: 0.6;
|
||||
user-select: none;
|
||||
color: var(--hk-codeblock-linenos-text-color);
|
||||
|
||||
&.show-splitter {
|
||||
border-right: 1px solid var(--hk-codeblock-border-color);
|
||||
margin-right: 1em;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* highlight */
|
||||
.hk-codeblock.hk-codeblock-show-highlight {
|
||||
pre {
|
||||
code.hk-codeblock-highlight {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
user-select: none;
|
||||
padding-top: var(--hk-codeblock-vertical-padding);
|
||||
padding-bottom: var(--hk-codeblock-vertical-padding);
|
||||
opacity: 0.2;
|
||||
|
||||
.line.highlight {
|
||||
background-color: var(--hk-codeblock-highlight-background-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* language */
|
||||
.hk-codeblock.hk-codeblock-show-language {
|
||||
pre {
|
||||
.hk-codeblock-language {
|
||||
opacity: 0.4;
|
||||
user-select: none;
|
||||
z-index: 1;
|
||||
margin: 6px;
|
||||
padding: 6px 8px;
|
||||
background-color: transparent;
|
||||
box-shadow: none;
|
||||
color: var(--hk-codeblock-language-text-color);
|
||||
font-size: var(--hk-codeblock-language-text-size);
|
||||
font-family: var(--hk-codeblock-language-text-font);
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
&:hover .hk-codeblock-language {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* copy button */
|
||||
.hk-codeblock.hk-codeblock-show-copybtn {
|
||||
pre {
|
||||
.copy-code-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* prompt */
|
||||
.hk-codeblock.hk-codeblock-show-prompt {
|
||||
pre {
|
||||
code.hk-codeblock-code {
|
||||
.command-line-prompt {
|
||||
display: block;
|
||||
float: left;
|
||||
font-size: 100%;
|
||||
text-align: right;
|
||||
user-select: none;
|
||||
|
||||
&>span::before {
|
||||
opacity: 0.6;
|
||||
content: ' ';
|
||||
display: block;
|
||||
margin-right: 0.6em;
|
||||
color: var(--hk-codeblock-prompt-text-color);
|
||||
}
|
||||
|
||||
&>span[data-prompt]::before {
|
||||
content: attr(data-prompt);
|
||||
}
|
||||
|
||||
&>span[data-continuation-prompt]::before {
|
||||
content: attr(data-continuation-prompt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* result */
|
||||
.hk-codeblock+.hk-codeblock.hk-codeblock-result {
|
||||
margin-top: calc(var(--hk-codeblock-margin) * (-1));
|
||||
border-top: 1px solid var(--hk-codeblock-border-color);
|
||||
|
||||
&::before {
|
||||
content: attr(data-result-prompt);
|
||||
display: block;
|
||||
color: var(--hk-codeblock-result-text-color);
|
||||
opacity: 0.6;
|
||||
font-size: 0.8em;
|
||||
padding-top: var(--hk-codeblock-vertical-padding);
|
||||
padding-left: var(--hk-codeblock-horizontal-padding);
|
||||
padding-right: var(--hk-codeblock-horizontal-padding);
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
|
||||
.hk-codeblock-result-code {
|
||||
color: var(--hk-codeblock-result-text-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
/*
|
||||
|
||||
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.
|
||||
|
||||
*/
|
||||
|
|
@ -10,15 +10,20 @@
|
|||
"moduleResolution": "node",
|
||||
"importHelpers": true,
|
||||
"isolatedModules": true,
|
||||
"strictNullChecks": true,
|
||||
"strictNullChecks": true,
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES5",
|
||||
"ES6",
|
||||
"ES7"
|
||||
]
|
||||
],
|
||||
"outDir": "./dist",
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts"
|
||||
"./src/**/*.ts"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"./dist"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,17 @@
|
|||
import { readFileSync, writeFileSync } from "fs";
|
||||
|
||||
const manifestFilePath = "./dist/manifest.json";
|
||||
const versionsFilePath = "./versions.json";
|
||||
|
||||
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"));
|
||||
let manifest = JSON.parse(readFileSync(manifestFilePath, "utf8"));
|
||||
const { minAppVersion } = manifest;
|
||||
manifest.version = targetVersion;
|
||||
writeFileSync("manifest.json", JSON.stringify(manifest, null, "\t"));
|
||||
writeFileSync(manifestFilePath, JSON.stringify(manifest, null, "\t"));
|
||||
|
||||
// update versions.json with target version and minAppVersion from manifest.json
|
||||
let versions = JSON.parse(readFileSync("versions.json", "utf8"));
|
||||
let versions = JSON.parse(readFileSync(versionsFilePath, "utf8"));
|
||||
versions[targetVersion] = minAppVersion;
|
||||
writeFileSync("versions.json", JSON.stringify(versions, null, "\t"));
|
||||
writeFileSync(versionsFilePath, JSON.stringify(versions, null, "\t"));
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
{
|
||||
"1.0.0": "0.15.0"
|
||||
}
|
||||