mirror of
https://github.com/tonytins/citylimits
synced 2025-08-16 14:34:43 -04:00
Lots of stuff I forgot to commit because Holidays
- D&D dice engine (see README) - Markdown support - Phantom camera
This commit is contained in:
parent
9589acd877
commit
2b41f84b05
125 changed files with 13170 additions and 23 deletions
17
addons/markdownlabel/.gitignore
vendored
Normal file
17
addons/markdownlabel/.gitignore
vendored
Normal file
|
@ -0,0 +1,17 @@
|
|||
# Godot 4+ specific ignores
|
||||
.godot/
|
||||
|
||||
# Godot-specific ignores
|
||||
.import/
|
||||
export.cfg
|
||||
export_presets.cfg
|
||||
*.import
|
||||
project.godot
|
||||
|
||||
# Imported translations (automatically generated from CSV files)
|
||||
*.translation
|
||||
|
||||
# Mono-specific ignores
|
||||
.mono/
|
||||
data_*/
|
||||
mono_crash.*.json
|
250
addons/markdownlabel/README.md
Normal file
250
addons/markdownlabel/README.md
Normal file
|
@ -0,0 +1,250 @@
|
|||
# MarkdownLabel
|
||||
|
||||
A custom [Godot](https://godotengine.org/) node that extends [RichTextLabel](https://docs.godotengine.org/en/stable/classes/class_richtextlabel.html) to use Markdown instead of BBCode.
|
||||
|
||||
### Contents
|
||||
|
||||
- [Disclaimer](#disclaimer)
|
||||
- [Installation](#installation)
|
||||
- [Usage](#usage)
|
||||
- [Basic syntax](#basic-syntax)
|
||||
- [Code](#code)
|
||||
- [Headers](#headers)
|
||||
- [Links](#links)
|
||||
- [Images](#images)
|
||||
- [Lists](#lists)
|
||||
- [Tables](#tables)
|
||||
- [Escaping characters](#escaping-characters)
|
||||
- [Limitations](#limitations)
|
||||
- [Unsupported syntax elements](#unsupported-syntax-elements)
|
||||
- [Performance](#performance)
|
||||
- [Acknowledgements](#acknowledgements)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
**This is a work in progress**. I created this for my own use and figured out someone else might as well have some use for it. Obviously using BBCode will be better performance-wise since it's natively integrated in Godot. But using Markdown is much easier to write and read, so it can save development time in many cases.
|
||||
|
||||
I coded this quickly and without previous knowledge of how to parse Markdown properly, so there might be some inefficiencies and bugs. Please report any unexpected behavior.
|
||||
|
||||
I might convert this to C++ code at some point, to improve performance.
|
||||
|
||||
### Intended use case
|
||||
|
||||
This node is very useful for static text that you want to display in your application. It's not recommended to use this for text which is dynamically modified at run time.
|
||||
|
||||
My initial use case that lead me to do this was to directly include text from files in my game, such as credits and patch notes, in a format that is easier to mantain for me. This has the added benefit of being able to use the same Markdown files that are displayed in a github repository, instead of having to make two versions of the same text in two different formats.
|
||||
|
||||
## Installation
|
||||
|
||||
1. Download the `addons` folder of this repository.
|
||||
2. Place it in your project's root folder.
|
||||
3. Go to `Project > Project Settings... > Plugins` and enable the MarkdownLabel plugin.
|
||||
4. Reload the project.
|
||||
|
||||
## Usage
|
||||
|
||||
Simply add a MarkdownLabel to the scene and write its `markdown_text` field in Markdown format.
|
||||
|
||||
In the RichTextLabel properties:
|
||||
- **`bbcode_enabled` property must be enabled**.
|
||||
- Do not touch the `text` property, since it's internally used by MarkdownLabel to properly format its text.
|
||||
- You can use the rest of its properties as normal.
|
||||
|
||||
You can still use BBCode tags that don't have a Markdown equivalent, such as `[color=green]underlined text[/color]`, allowing you to have the full functionality of RichTextLabel with the simplicity and readibility of Markdown.
|
||||
|
||||

|
||||
*An example of the node being used to display this Markdown file.*
|
||||
|
||||
### Basic syntax
|
||||
|
||||
The basic Markdown syntax works in the standard way:
|
||||
|
||||
```
|
||||
Markdown text ................ -> BBCode equivalent
|
||||
-------------------------------||------------------
|
||||
**Bold** or __bold__ ......... -> [b]Bold[/b] or [b]bold[/b]
|
||||
*Italics* or _italics_ ....... -> [i]Italics[/i] or [i]italics[/i]
|
||||
***Nested*** *__emphasis__* .. -> [b][i]Nested[/i][b] [i][b]emphasis[/b][/i]
|
||||
~~Strike-through~~ ........... -> [s]Strike-through[/s]
|
||||
```
|
||||
|
||||
### Code
|
||||
|
||||
You can display code in-line by surrounding text with any number of backticks (\`), and you can display code in multiple lines (also called a fenced code block) by placing a line containing just three or more backticks (\`\`\`) or tildes (\~\~\~) above and below your code block.
|
||||
|
||||
Examples:
|
||||
```
|
||||
Markdown text ................. -> BBCode equivalent
|
||||
--------------------------------||------------------
|
||||
The following is `in-line code` -> The following is [code]in-line code[/code]
|
||||
This is also ``in-line code`` -> The following is [code]in-line code[/code]
|
||||
|
||||
~~~ .......... -> [code]
|
||||
This is a .......... -> This is a
|
||||
multiline codeblock .......... -> multiline codeblock
|
||||
~~~ .......... -> [/code]
|
||||
|
||||
```
|
||||
|
||||
**Important**: note that in-line code and code blocks won't do anything with Godot's default font, since it doesn't have a monospace variant. As described in [Godot's BBCode reference](https://docs.godotengine.org/en/stable/tutorials/ui/bbcode_in_richtextlabel.html#reference): "The monospaced (`[code]`) tag only works if a custom font is set up in the RichTextLabel node's theme overrides. Otherwise, monospaced text will use the regular font".
|
||||
|
||||
### Headers
|
||||
|
||||
MarkdownLabel supports headers, although RichTextLabel doesn't. By default, a line defined as a header will have its font size scaled by a pre-defined amount.
|
||||
|
||||
To define a line as a header, begin it with any number of consecutive hash symbols (#) and follow it with the title of your header. The number of hash symbols defines the level of the header. The maximum supported level is six..
|
||||
|
||||
Example:
|
||||
```
|
||||
Markdown text:
|
||||
## This is a second-level header
|
||||
|
||||
BBCode equivalent:
|
||||
[font_size=27]This is a second-level header[/font_size]
|
||||
```
|
||||
where the `27` in `[font_size=27]` comes from multiplying the set `h2.font_size` (`1.714` by default) by the current `normal_font_size` (`16` by default).
|
||||
|
||||
You can optionally set custom sizes and formatting (bold, italics, and underline) for each header level individually. To do so:
|
||||
|
||||
- In the inspector, open the "Header formats" category, click on the resource associated with the desired header level, and customize the properties there.
|
||||
- In script, access those properties through the `h1`, `h2`, etc. properties. Example: `$YourMarkdownLabel.h3.is_italic = true` will set all level-3 headers within `$YourMarkdownLabel` to be displayed as italics.
|
||||
|
||||
Of course, you can also use basic formatting within the headers (e.g. `### Header with **bold** and *italic* words`).
|
||||
|
||||
### Links
|
||||
|
||||
Links follow the standard Markdown syntax of `[text to display](example.com)`. Additionally, you can add tooltips to your links with `[text to display](example.com "Some tooltip")`.
|
||||
|
||||
"Autolinks" are also supported with their standard syntax: `<example.com>`, and `<mail@example.com>` for mail autolinks.
|
||||
|
||||
Keep in mind that, in Godot, **links do nothing by default**. MarkdownLabel treats them the say way (may be changed in the future). See the [RichTextLabel reference](https://docs.godotengine.org/en/stable/tutorials/ui/bbcode_in_richtextlabel.html#doc-bbcode-in-richtextlabel-handling-url-tag-clicks) for more info.
|
||||
|
||||
```
|
||||
Markdown text .............................. -> BBCode equivalent
|
||||
---------------------------------------------||------------------
|
||||
[this is a link](example.com) .............. -> [url=example.com]this is a link[/url]
|
||||
[this is a link](example.com "Example page") -> [hint=Example url][url=example.com]this is a link[/url][/hint]
|
||||
<example.com> .............................. -> [url]example.com[/url]
|
||||
<mail@example.com> ......................... -> [url=mailto:mail@example.com]mail@example.com[/url]
|
||||
```
|
||||
|
||||
### Images
|
||||
|
||||
Images use the same syntax as links but preceded by an exclamation mark (!):
|
||||
|
||||
```
|
||||
Markdown text .............................................. -> BBCode equivalent
|
||||
-------------------------------------------------------------||------------------
|
||||
 ................... -> [img]res://some/path.png[/img]
|
||||
 -> [hint=This is a tooltip][img]res://some/path.png[/img][/hint]
|
||||
```
|
||||
However, Godot's BBCode doesn't support alt text for images, so what you put inside the square brackets doesn't affect the end result. You can use it for your own clarity, though.
|
||||
|
||||
For advanced usage (setting width, height, and other options), use the BBCode `[img]` tag instead, as described in [Godot's BBCode reference](https://docs.godotengine.org/en/stable/tutorials/ui/bbcode_in_richtextlabel.html#reference).
|
||||
|
||||
### Lists
|
||||
|
||||
Unordered list elements begin with a dash (-), asterisk (*), or plus sign (+) followed by a space.
|
||||
|
||||
Ordered list elements begin with a number from 1 to 9 followed by a single dot and a space.
|
||||
|
||||
To begin a list, you must write the first element without indentation and, in the case of ordered lists, the first element must begin with the number 1.
|
||||
|
||||
From there, you add elements in consecutive lines (do not leave blank lines between elements), and you can open nested lists by indenting new elements any number of spaces or tabs.
|
||||
|
||||
Examples:
|
||||
|
||||
Markdown text:
|
||||
```
|
||||
1. First element of an unordered list
|
||||
2. Second element
|
||||
1. Nested element
|
||||
1. Third element. The number at the beginning doesn't need to match the actual order. It's only relevant for the first element.
|
||||
- You can also nest unordered lists inside ordered lists, and viceversa
|
||||
1. This is a nested list inside another nested list.
|
||||
```
|
||||
BBCode equivalent:
|
||||
```
|
||||
[ol]First element of an unordered list
|
||||
Second element
|
||||
[ol]Nested element[/ol]
|
||||
Third element. The number at the beginning doesn't need to match the actual order. It's only relevant for the first element.
|
||||
[ul]You can also nest unordered lists inside ordered lists, and viceversa
|
||||
[ol]This is a nested list inside another nested list.[/ol]
|
||||
[/ul][/ol]
|
||||
```
|
||||
|
||||
### Tables
|
||||
|
||||
Tables are constructed by separating columns with pipes (`|`).
|
||||
|
||||
Example:
|
||||
|
||||
Markdown text:
|
||||
````
|
||||
| cell1 | cell2 |
|
||||
| cell3 | cell4 |
|
||||
````
|
||||
|
||||
BBCode equivalent:
|
||||
````
|
||||
[table=2]
|
||||
[cell]cell1[/cell][cell]cell2[/cell]
|
||||
[cell]cell3[/cell][cell]cell4[/cell]
|
||||
[/table]
|
||||
````
|
||||
|
||||
Note that [delimiter rows](https://github.github.com/gfm/#delimiter-row) are optional and will be ignored, since Godot's BBCode doesn't support cell alignment.
|
||||
|
||||
Example:
|
||||
````
|
||||
| cell1 | cell2 |
|
||||
| ----: | :---- |
|
||||
| cell3 | cell4 |
|
||||
````
|
||||
The above Markdown table will produce the same BBCode output as the previous example.
|
||||
|
||||
For advanced usage (setting ratio, border, background, etc.), use the BBCode `[table]` tag instead, as described in [Godot's BBCode reference](https://docs.godotengine.org/en/stable/tutorials/ui/bbcode_in_richtextlabel.html#reference).
|
||||
|
||||
### Escaping characters
|
||||
|
||||
You can escape characters using a backlash if you don't want them to form a Markdown syntax element. You can escape backlashes if you don't want them to escape the following character. You can't escape characters inside in-line or fenced code, since the string will be displayed as-is. You also don't need to escape characters inside a link or image url.
|
||||
|
||||
Examples:
|
||||
```
|
||||
Markdown text ............................ -> BBCode equivalent
|
||||
-------------------------------------------||------------------
|
||||
These \**outer asterisks*\* are escaped .. -> These *[i]outer asterisks[/i]* are escaped
|
||||
This \\*asterisk* is not escaped ......... -> \[i]This asterisk[/i] is not escaped
|
||||
`This \\*asterisk* is inside in-line code` -> [code]This \\*asterisk* is inside in-line code[/code]
|
||||
[Link](url_with_backlashes.net) .......... -> [url=url_with_backlashes.net]Link[/url]
|
||||
```
|
||||
|
||||
Note: to escape an ordered list, you must escape the dot that follows the number, e.g. `1\. Not a list`.
|
||||
|
||||
Keep in mind that, if you are writing text inside a script, you will have to "double escape" backlashes, since you are writing in a string. Some other characters, such as double-quotes, also need in-script escaping:
|
||||
|
||||
- In-script: `\\*`, `\\\"`
|
||||
- In-editor: `\*`, `\"`
|
||||
- Result: `*`, `"`
|
||||
|
||||
## Limitations
|
||||
|
||||
Keep in mind that this is not supposed to be a full Markdown implementation, it just provides a Markdown interface to Godot's BBCode support and, as such, is limited by it.
|
||||
|
||||
If encountering any unreported bug or unexpected bahaviour, please ensure that your Markdown is written as clean as possible, following best practices (I wrote this primarily taking [Commonmark](https://commonmark.org/) and [Github-flavoured Markdown](https://github.github.com/gfm/) as reference, but it has its own peculiarities due to the use of Godot's BBCode).
|
||||
|
||||
### Unsupported syntax elements
|
||||
|
||||
The following Markdown syntax elements are not supported because Godot's BBCode does not support them:
|
||||
- Quotes
|
||||
- Horizontal rules
|
||||
- Reference links
|
||||
|
||||
### Performance
|
||||
|
||||
This node basically parses the whole text, converting it from Markdown to BBCode at runtime, so it may produce performance issues with some extreme usages, such as very large and heavily-formatted texts or updating a heavily-formatted text very frequently. That already can happen with BBCode, though, so in that case, you are probably better off [using RichTextLabel's functions](https://docs.godotengine.org/en/stable/tutorials/ui/bbcode_in_richtextlabel.html#using-push-tag-and-pop-functions-instead-of-bbcode) instead of writing the formatting directly in-text.
|
||||
|
||||
### Acknowledgements
|
||||
|
||||
The syntax and implementation of MarkdownLabel is largely based on [Github-flavored Markdown](https://github.github.com/gfm/) and [CommonMark](https://commonmark.org/), with its own quirks to accomodate it within [Godot's RichTextLabel BBCode](https://docs.godotengine.org/en/stable/tutorials/ui/bbcode_in_richtextlabel.html).
|
BIN
addons/markdownlabel/assets/screenshot.png
Normal file
BIN
addons/markdownlabel/assets/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 176 KiB |
5
addons/markdownlabel/example.gd
Normal file
5
addons/markdownlabel/example.gd
Normal file
|
@ -0,0 +1,5 @@
|
|||
extends Control
|
||||
|
||||
|
||||
func _ready() -> void:
|
||||
$MarkdownLabel.display_file("res://addons/markdownlabel/README.md")
|
77
addons/markdownlabel/example.tscn
Normal file
77
addons/markdownlabel/example.tscn
Normal file
|
@ -0,0 +1,77 @@
|
|||
[gd_scene load_steps=15 format=3 uid="uid://bka0d50qmnb8y"]
|
||||
|
||||
[ext_resource type="Script" path="res://addons/markdownlabel/example.gd" id="1_7b8dd"]
|
||||
[ext_resource type="Script" path="res://addons/markdownlabel/markdownlabel.gd" id="2_opcio"]
|
||||
[ext_resource type="Script" path="res://addons/markdownlabel/header_formats/h1_format.gd" id="3_kbjha"]
|
||||
[ext_resource type="Script" path="res://addons/markdownlabel/header_formats/h2_format.gd" id="4_tqhuu"]
|
||||
[ext_resource type="Script" path="res://addons/markdownlabel/header_formats/h3_format.gd" id="5_us0p7"]
|
||||
[ext_resource type="Script" path="res://addons/markdownlabel/header_formats/h4_format.gd" id="6_8ublj"]
|
||||
[ext_resource type="Script" path="res://addons/markdownlabel/header_formats/h5_format.gd" id="7_42de6"]
|
||||
[ext_resource type="Script" path="res://addons/markdownlabel/header_formats/h6_format.gd" id="8_y8fds"]
|
||||
|
||||
[sub_resource type="Resource" id="Resource_r7ev3"]
|
||||
script = ExtResource("3_kbjha")
|
||||
font_size = 2.285
|
||||
is_bold = false
|
||||
is_italic = false
|
||||
is_underlined = false
|
||||
|
||||
[sub_resource type="Resource" id="Resource_qh6ic"]
|
||||
script = ExtResource("4_tqhuu")
|
||||
font_size = 1.714
|
||||
is_bold = false
|
||||
is_italic = false
|
||||
is_underlined = false
|
||||
|
||||
[sub_resource type="Resource" id="Resource_qx73p"]
|
||||
script = ExtResource("5_us0p7")
|
||||
font_size = 1.428
|
||||
is_bold = false
|
||||
is_italic = false
|
||||
is_underlined = false
|
||||
|
||||
[sub_resource type="Resource" id="Resource_yx0wh"]
|
||||
script = ExtResource("6_8ublj")
|
||||
font_size = 1.142
|
||||
is_bold = false
|
||||
is_italic = false
|
||||
is_underlined = false
|
||||
|
||||
[sub_resource type="Resource" id="Resource_1ovcl"]
|
||||
script = ExtResource("7_42de6")
|
||||
font_size = 1.0
|
||||
is_bold = false
|
||||
is_italic = false
|
||||
is_underlined = false
|
||||
|
||||
[sub_resource type="Resource" id="Resource_fj0e0"]
|
||||
script = ExtResource("8_y8fds")
|
||||
font_size = 0.857
|
||||
is_bold = false
|
||||
is_italic = false
|
||||
is_underlined = false
|
||||
|
||||
[node name="Example" type="Control"]
|
||||
layout_mode = 3
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
script = ExtResource("1_7b8dd")
|
||||
|
||||
[node name="MarkdownLabel" type="RichTextLabel" parent="."]
|
||||
layout_mode = 1
|
||||
anchors_preset = 15
|
||||
anchor_right = 1.0
|
||||
anchor_bottom = 1.0
|
||||
grow_horizontal = 2
|
||||
grow_vertical = 2
|
||||
bbcode_enabled = true
|
||||
script = ExtResource("2_opcio")
|
||||
h1 = SubResource("Resource_r7ev3")
|
||||
h2 = SubResource("Resource_qh6ic")
|
||||
h3 = SubResource("Resource_qx73p")
|
||||
h4 = SubResource("Resource_yx0wh")
|
||||
h5 = SubResource("Resource_1ovcl")
|
||||
h6 = SubResource("Resource_fj0e0")
|
32
addons/markdownlabel/header_formats/h1_format.gd
Normal file
32
addons/markdownlabel/header_formats/h1_format.gd
Normal file
|
@ -0,0 +1,32 @@
|
|||
class_name H1Format
|
||||
extends Resource
|
||||
|
||||
## Relative font size of this header level (will be multiplied by [code]normal_font_size[/code])
|
||||
@export var font_size: float = 2.285 : set = _set_font_size
|
||||
## Whether this header level is drawn as bold or not
|
||||
@export var is_bold := false : set = _set_is_bold
|
||||
## Whether this header level is drawn as italics or not
|
||||
@export var is_italic := false : set = _set_is_italic
|
||||
## Whether this header level is underlined or not
|
||||
@export var is_underlined := false : set = _set_is_underlined
|
||||
|
||||
signal _updated
|
||||
|
||||
func _init() -> void:
|
||||
resource_local_to_scene = true
|
||||
|
||||
func _set_font_size(new_font_size: float) -> void:
|
||||
font_size = new_font_size
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_bold(new_is_bold: bool) -> void:
|
||||
is_bold = new_is_bold
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_italic(new_is_italic: bool) -> void:
|
||||
is_italic = new_is_italic
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_underlined(new_is_underlined: bool) -> void:
|
||||
is_underlined = new_is_underlined
|
||||
_updated.emit()
|
32
addons/markdownlabel/header_formats/h2_format.gd
Normal file
32
addons/markdownlabel/header_formats/h2_format.gd
Normal file
|
@ -0,0 +1,32 @@
|
|||
class_name H2Format
|
||||
extends Resource
|
||||
|
||||
## Relative font size of this header level (will be multiplied by [code]normal_font_size[/code])
|
||||
@export var font_size: float = 1.714 : set = _set_font_size
|
||||
## Whether this header level is drawn as bold or not
|
||||
@export var is_bold := false : set = _set_is_bold
|
||||
## Whether this header level is drawn as italics or not
|
||||
@export var is_italic := false : set = _set_is_italic
|
||||
## Whether this header level is underlined or not
|
||||
@export var is_underlined := false : set = _set_is_underlined
|
||||
|
||||
signal _updated
|
||||
|
||||
func _init() -> void:
|
||||
resource_local_to_scene = true
|
||||
|
||||
func _set_font_size(new_font_size: float) -> void:
|
||||
font_size = new_font_size
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_bold(new_is_bold: bool) -> void:
|
||||
is_bold = new_is_bold
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_italic(new_is_italic: bool) -> void:
|
||||
is_italic = new_is_italic
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_underlined(new_is_underlined: bool) -> void:
|
||||
is_underlined = new_is_underlined
|
||||
_updated.emit()
|
32
addons/markdownlabel/header_formats/h3_format.gd
Normal file
32
addons/markdownlabel/header_formats/h3_format.gd
Normal file
|
@ -0,0 +1,32 @@
|
|||
class_name H3Format
|
||||
extends Resource
|
||||
|
||||
## Relative font size of this header level (will be multiplied by [code]normal_font_size[/code])
|
||||
@export var font_size: float = 1.428 : set = _set_font_size
|
||||
## Whether this header level is drawn as bold or not
|
||||
@export var is_bold := false : set = _set_is_bold
|
||||
## Whether this header level is drawn as italics or not
|
||||
@export var is_italic := false : set = _set_is_italic
|
||||
## Whether this header level is underlined or not
|
||||
@export var is_underlined := false : set = _set_is_underlined
|
||||
|
||||
signal _updated
|
||||
|
||||
func _init() -> void:
|
||||
resource_local_to_scene = true
|
||||
|
||||
func _set_font_size(new_font_size: float) -> void:
|
||||
font_size = new_font_size
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_bold(new_is_bold: bool) -> void:
|
||||
is_bold = new_is_bold
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_italic(new_is_italic: bool) -> void:
|
||||
is_italic = new_is_italic
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_underlined(new_is_underlined: bool) -> void:
|
||||
is_underlined = new_is_underlined
|
||||
_updated.emit()
|
32
addons/markdownlabel/header_formats/h4_format.gd
Normal file
32
addons/markdownlabel/header_formats/h4_format.gd
Normal file
|
@ -0,0 +1,32 @@
|
|||
class_name H4Format
|
||||
extends Resource
|
||||
|
||||
## Relative font size of this header level (will be multiplied by [code]normal_font_size[/code])
|
||||
@export var font_size: float = 1.142 : set = _set_font_size
|
||||
## Whether this header level is drawn as bold or not
|
||||
@export var is_bold := false : set = _set_is_bold
|
||||
## Whether this header level is drawn as italics or not
|
||||
@export var is_italic := false : set = _set_is_italic
|
||||
## Whether this header level is underlined or not
|
||||
@export var is_underlined := false : set = _set_is_underlined
|
||||
|
||||
signal _updated
|
||||
|
||||
func _init() -> void:
|
||||
resource_local_to_scene = true
|
||||
|
||||
func _set_font_size(new_font_size: float) -> void:
|
||||
font_size = new_font_size
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_bold(new_is_bold: bool) -> void:
|
||||
is_bold = new_is_bold
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_italic(new_is_italic: bool) -> void:
|
||||
is_italic = new_is_italic
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_underlined(new_is_underlined: bool) -> void:
|
||||
is_underlined = new_is_underlined
|
||||
_updated.emit()
|
32
addons/markdownlabel/header_formats/h5_format.gd
Normal file
32
addons/markdownlabel/header_formats/h5_format.gd
Normal file
|
@ -0,0 +1,32 @@
|
|||
class_name H5Format
|
||||
extends Resource
|
||||
|
||||
## Relative font size of this header level (will be multiplied by [code]normal_font_size[/code])
|
||||
@export var font_size: float = 1 : set = _set_font_size
|
||||
## Whether this header level is drawn as bold or not
|
||||
@export var is_bold := false : set = _set_is_bold
|
||||
## Whether this header level is drawn as italics or not
|
||||
@export var is_italic := false : set = _set_is_italic
|
||||
## Whether this header level is underlined or not
|
||||
@export var is_underlined := false : set = _set_is_underlined
|
||||
|
||||
signal _updated
|
||||
|
||||
func _init() -> void:
|
||||
resource_local_to_scene = true
|
||||
|
||||
func _set_font_size(new_font_size: float) -> void:
|
||||
font_size = new_font_size
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_bold(new_is_bold: bool) -> void:
|
||||
is_bold = new_is_bold
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_italic(new_is_italic: bool) -> void:
|
||||
is_italic = new_is_italic
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_underlined(new_is_underlined: bool) -> void:
|
||||
is_underlined = new_is_underlined
|
||||
_updated.emit()
|
32
addons/markdownlabel/header_formats/h6_format.gd
Normal file
32
addons/markdownlabel/header_formats/h6_format.gd
Normal file
|
@ -0,0 +1,32 @@
|
|||
class_name H6Format
|
||||
extends Resource
|
||||
|
||||
## Relative font size of this header level (will be multiplied by [code]normal_font_size[/code])
|
||||
@export var font_size: float = 0.857 : set = _set_font_size
|
||||
## Whether this header level is drawn as bold or not
|
||||
@export var is_bold := false : set = _set_is_bold
|
||||
## Whether this header level is drawn as italics or not
|
||||
@export var is_italic := false : set = _set_is_italic
|
||||
## Whether this header level is underlined or not
|
||||
@export var is_underlined := false : set = _set_is_underlined
|
||||
|
||||
signal _updated
|
||||
|
||||
func _init() -> void:
|
||||
resource_local_to_scene = true
|
||||
|
||||
func _set_font_size(new_font_size: float) -> void:
|
||||
font_size = new_font_size
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_bold(new_is_bold: bool) -> void:
|
||||
is_bold = new_is_bold
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_italic(new_is_italic: bool) -> void:
|
||||
is_italic = new_is_italic
|
||||
_updated.emit()
|
||||
|
||||
func _set_is_underlined(new_is_underlined: bool) -> void:
|
||||
is_underlined = new_is_underlined
|
||||
_updated.emit()
|
36
addons/markdownlabel/icon.svg
Normal file
36
addons/markdownlabel/icon.svg
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
width="16"
|
||||
version="1.1"
|
||||
id="svg1"
|
||||
sodipodi:docname="icon.svg"
|
||||
xml:space="preserve"
|
||||
inkscape:version="1.3 (0e150ed6c4, 2023-07-21)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs1" /><sodipodi:namedview
|
||||
id="namedview1"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1"
|
||||
inkscape:zoom="18.141708"
|
||||
inkscape:cx="-10.3353"
|
||||
inkscape:cy="8.4611657"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1017"
|
||||
inkscape:window-x="-8"
|
||||
inkscape:window-y="-8"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1" /><path
|
||||
id="path1"
|
||||
style="fill:#8eef97;fill-opacity:1"
|
||||
d="M 6,3 C 5.7348055,3.0000566 5.4804613,3.1054195 5.2929688,3.2929688 l -4,4 c -0.3903816,0.3904995 -0.3903816,1.0235629 0,1.4140624 l 4,3.9999998 C 5.4804613,12.89458 5.7348055,12.999943 6,13 h 8 c 0.552284,0 1,-0.447716 1,-1 V 4 C 15,3.4477159 14.552284,3 14,3 Z M 3.7265625,5.6132812 H 5.1621094 L 6.5976562,7.4082031 8.0332031,5.6132812 H 9.46875 V 10.494141 H 8.0332031 V 7.6953125 L 6.5976562,9.4902344 5.1621094,7.6953125 V 10.494141 H 3.7265625 Z m 8.1835935,0 h 1.435547 V 8.0546875 H 14.78125 L 12.628906,10.566406 10.474609,8.0546875 h 1.435547 z"
|
||||
sodipodi:nodetypes="ccccccsssscccccccccccccccccccccc" /></svg>
|
After Width: | Height: | Size: 1.7 KiB |
599
addons/markdownlabel/markdownlabel.gd
Normal file
599
addons/markdownlabel/markdownlabel.gd
Normal file
|
@ -0,0 +1,599 @@
|
|||
@tool
|
||||
class_name MarkdownLabel
|
||||
extends RichTextLabel
|
||||
## A control for displaying Markdown-style text.
|
||||
##
|
||||
## A custom node that extends [RichTextLabel] to use Markdown instead of BBCode.
|
||||
## [br][br]
|
||||
## [b][u]Usage:[/u][/b]
|
||||
## Simply add a MarkdownLabel to the scene and write its [member markdown_text] field in Markdown format.
|
||||
## [br][br]
|
||||
## On its [RichTextLabel] properties: [member RichTextLabel.bbcode_enabled] property must be enabled. Do not touch the [member RichTextLabel.text] property, since it's used by MarkdownLabel to properly format its text. You can use the rest of its properties as normal.
|
||||
## [br][br]
|
||||
## You can still use BBCode tags that don't have a Markdown equivalent, such as `[u]underlined text[/u]`, allowing you to have the full functionality of RichTextLabel with the simplicity and readibility of Markdown.
|
||||
## [br][br]
|
||||
## Check out the full guide in the Github repo readme file (linked below). If encountering any unreported bug or unexpected bahaviour, please ensure that your Markdown is written as clean as possible, following best practices.
|
||||
##
|
||||
## @tutorial(Github repository): https://github.com/daenvil/MarkdownLabel
|
||||
|
||||
const _ESCAPE_PLACEHOLDER := ";$\uFFFD:%s$;"
|
||||
const _ESCAPEABLE_CHARACTERS := "\\*_~`[]()\"<>#-+.!"
|
||||
const _ESCAPEABLE_CHARACTERS_REGEX := "[\\\\\\*\\_\\~`\\[\\]\\(\\)\\\"\\<\\>#\\-\\+\\.\\!]"
|
||||
|
||||
# Public:
|
||||
## The text to be displayed in Markdown format.
|
||||
@export_multiline var markdown_text: String : set = _set_markdown_text
|
||||
|
||||
@export_group("Header formats")
|
||||
## Formatting options for level-1 headers
|
||||
@export var h1 := H1Format.new() : set = _set_h1_format
|
||||
## Formatting options for level-2 headers
|
||||
@export var h2 := H2Format.new() : set = _set_h2_format
|
||||
## Formatting options for level-3 headers
|
||||
@export var h3 := H3Format.new() : set = _set_h3_format
|
||||
## Formatting options for level-4 headers
|
||||
@export var h4 := H4Format.new() : set = _set_h4_format
|
||||
## Formatting options for level-5 headers
|
||||
@export var h5 := H5Format.new() : set = _set_h5_format
|
||||
## Formatting options for level-6 headers
|
||||
@export var h6 := H6Format.new() : set = _set_h6_format
|
||||
|
||||
# Private:
|
||||
var _converted_text: String
|
||||
var _indent_level: int
|
||||
var _escaped_characters_map := {}
|
||||
var _within_table := false
|
||||
var _table_row := -1
|
||||
var _line_break := true
|
||||
var _debug_mode := false
|
||||
|
||||
# Built-in methods:
|
||||
func _init(markdown_text: String = "") -> void:
|
||||
bbcode_enabled = true
|
||||
self.markdown_text = markdown_text
|
||||
|
||||
func _ready() -> void:
|
||||
h1.connect("_updated",_update)
|
||||
h1.connect("changed",_update)
|
||||
h2.connect("_updated",_update)
|
||||
h2.connect("changed",_update)
|
||||
h3.connect("_updated",_update)
|
||||
h3.connect("changed",_update)
|
||||
h4.connect("_updated",_update)
|
||||
h4.connect("changed",_update)
|
||||
h5.connect("_updated",_update)
|
||||
h5.connect("changed",_update)
|
||||
h6.connect("_updated",_update)
|
||||
h6.connect("changed",_update)
|
||||
if Engine.is_editor_hint():
|
||||
bbcode_enabled = true
|
||||
#else:
|
||||
#pass
|
||||
|
||||
# Should hide properties in the editor, not working for some reason:
|
||||
#func _validate_property(property: Dictionary):
|
||||
# print(property.name)
|
||||
# if property.name in ["bbcode_enabled", "text"]:
|
||||
# property.usage = PROPERTY_USAGE_NO_EDITOR
|
||||
|
||||
# Public methods:
|
||||
## Reads the specified file and displays it as markdown.
|
||||
func display_file(file_path: String):
|
||||
markdown_text = FileAccess.get_file_as_string(file_path)
|
||||
|
||||
#Private methods:
|
||||
func _update() -> void:
|
||||
text = _convert_markdown(markdown_text)
|
||||
queue_redraw()
|
||||
|
||||
func _set_markdown_text(new_text: String):
|
||||
markdown_text = new_text
|
||||
_update()
|
||||
|
||||
func _set_h1_format(new_format: H1Format):
|
||||
h1 = new_format
|
||||
_update()
|
||||
|
||||
func _set_h2_format(new_format: H2Format):
|
||||
h2 = new_format
|
||||
_update()
|
||||
|
||||
func _set_h3_format(new_format: H3Format):
|
||||
h3 = new_format
|
||||
_update()
|
||||
|
||||
func _set_h4_format(new_format: H4Format):
|
||||
h4 = new_format
|
||||
_update()
|
||||
|
||||
func _set_h5_format(new_format: H5Format):
|
||||
h5 = new_format
|
||||
_update()
|
||||
|
||||
func _set_h6_format(new_format: H6Format):
|
||||
h6 = new_format
|
||||
_update()
|
||||
|
||||
func _convert_markdown(source_text = "") -> String:
|
||||
if not bbcode_enabled:
|
||||
push_warning("WARNING: MarkdownLabel node will not format Markdown syntax if it doesn't have 'bbcode_enabled=true'")
|
||||
return source_text
|
||||
_converted_text = ""
|
||||
var regex = RegEx.new()
|
||||
|
||||
var lines = source_text.split("\n")
|
||||
_indent_level = -1
|
||||
var indent_spaces := []
|
||||
var indent_types := []
|
||||
var iline := 0
|
||||
var within_backtick_block := false
|
||||
var within_tilde_block := false
|
||||
var within_code_block := false
|
||||
var current_code_block_char_count: int
|
||||
_within_table = false
|
||||
_table_row = -1
|
||||
_line_break = true
|
||||
|
||||
for line in lines:
|
||||
line = line.trim_suffix("\r")
|
||||
_debug("Parsing line: '%s'"%line)
|
||||
within_code_block = within_tilde_block or within_backtick_block
|
||||
if iline > 0 and _line_break:
|
||||
_converted_text += "\n"
|
||||
_line_break = true
|
||||
iline+=1
|
||||
if not within_tilde_block and _denotes_fenced_code_block(line,"`"):
|
||||
if within_backtick_block:
|
||||
if line.strip_edges().length() >= current_code_block_char_count:
|
||||
_converted_text = _converted_text.trim_suffix("\n")
|
||||
_converted_text += "[/code]"
|
||||
within_backtick_block = false
|
||||
_debug("... closing backtick block")
|
||||
continue
|
||||
else:
|
||||
_converted_text += "[code]"
|
||||
within_backtick_block = true
|
||||
current_code_block_char_count = line.strip_edges().length()
|
||||
_debug("... opening backtick block")
|
||||
continue
|
||||
elif not within_backtick_block and _denotes_fenced_code_block(line,"~"):
|
||||
if within_tilde_block:
|
||||
if line.strip_edges().length() >= current_code_block_char_count:
|
||||
_converted_text = _converted_text.trim_suffix("\n")
|
||||
_converted_text += "[/code]"
|
||||
within_tilde_block = false
|
||||
_debug("... closing tilde block")
|
||||
continue
|
||||
else:
|
||||
_converted_text += "[code]"
|
||||
within_tilde_block = true
|
||||
current_code_block_char_count = line.strip_edges().length()
|
||||
_debug("... opening tilde block")
|
||||
continue
|
||||
if within_code_block: #ignore any formatting inside code block
|
||||
_converted_text += _escape_bbcode(line)
|
||||
continue
|
||||
|
||||
var _processed_line = line
|
||||
|
||||
# Escape characters:
|
||||
regex.compile("\\\\"+_ESCAPEABLE_CHARACTERS_REGEX)
|
||||
while true:
|
||||
var result := regex.search(_processed_line)
|
||||
if not result:
|
||||
break
|
||||
var _start := result.get_start()
|
||||
var _escaped_char := result.get_string()[1]
|
||||
if not _escaped_char in _escaped_characters_map:
|
||||
_escaped_characters_map[_escaped_char] = _escaped_characters_map.size()
|
||||
_processed_line = _processed_line.erase(_start,2).insert(_start,_ESCAPE_PLACEHOLDER % _escaped_characters_map[_escaped_char])
|
||||
|
||||
# Tables:
|
||||
_processed_line = _process_table_syntax(_processed_line)
|
||||
|
||||
# Lists:
|
||||
_processed_line = _process_list_syntax(_processed_line,indent_spaces,indent_types)
|
||||
|
||||
# In-line code
|
||||
regex.compile("(`+)(.+?)\\1")
|
||||
while true:
|
||||
var result = regex.search(_processed_line)
|
||||
if result:
|
||||
var _start = result.get_start()
|
||||
var _end = result.get_end()
|
||||
var unescaped_content := _reset_escaped_chars(result.get_string(2),true)
|
||||
unescaped_content = _escape_bbcode(unescaped_content)
|
||||
unescaped_content = _escape_chars(unescaped_content)
|
||||
_processed_line = _processed_line.erase(_start,_end-_start).insert(_start,"[code]%s[/code]"%unescaped_content)
|
||||
_debug("... in-line code: "+unescaped_content)
|
||||
else:
|
||||
break
|
||||
|
||||
# Images
|
||||
var img_pattern := "\\!\\[(.*?)\\]\\((.*?)\\)"
|
||||
while true:
|
||||
regex.compile(img_pattern)
|
||||
var result = regex.search(_processed_line)
|
||||
var found_proper_match := false
|
||||
if result:
|
||||
var _start = result.get_start()
|
||||
var _end = result.get_end()
|
||||
regex.compile("\\[(.*?)\\]")
|
||||
var texts = regex.search_all(result.get_string())
|
||||
for _text in texts:
|
||||
if result.get_string()[_text.get_end()] != "(":
|
||||
continue
|
||||
found_proper_match = true
|
||||
# Check if link has a title:
|
||||
regex.compile("\\\"(.*?)\\\"")
|
||||
var title_result = regex.search(result.get_string(2))
|
||||
var title: String
|
||||
var url := result.get_string(2)
|
||||
if title_result:
|
||||
title = title_result.get_string(1)
|
||||
url = url.rstrip(" ").trim_suffix(title_result.get_string()).rstrip(" ")
|
||||
url = _escape_chars(url)
|
||||
_processed_line = _processed_line.erase(_start,_end-_start).insert(_start,"[img]%s[/img]" % url)
|
||||
if title_result and title:
|
||||
_processed_line = _processed_line.insert(_start+12+url.length()+_text.get_string(1).length(),"[/hint]").insert(_start,"[hint=%s]"%title)
|
||||
_debug("... hyperlink: "+result.get_string())
|
||||
break
|
||||
if not found_proper_match:
|
||||
break
|
||||
|
||||
# Links
|
||||
var link_pattern := "\\[(.*?)\\]\\((.*?)\\)"
|
||||
while true:
|
||||
regex.compile(link_pattern)
|
||||
var result = regex.search(_processed_line)
|
||||
var found_proper_match := false
|
||||
if result:
|
||||
var _start = result.get_start()
|
||||
var _end = result.get_end()
|
||||
regex.compile("\\[(.*?)\\]")
|
||||
var texts = regex.search_all(result.get_string())
|
||||
for _text in texts:
|
||||
if result.get_string()[_text.get_end()] != "(":
|
||||
continue
|
||||
found_proper_match = true
|
||||
# Check if link has a title:
|
||||
regex.compile("\\\"(.*?)\\\"")
|
||||
var title_result = regex.search(result.get_string(2))
|
||||
var title: String
|
||||
var url := result.get_string(2)
|
||||
if title_result:
|
||||
title = title_result.get_string(1)
|
||||
url = url.rstrip(" ").trim_suffix(title_result.get_string()).rstrip(" ")
|
||||
url = _escape_chars(url)
|
||||
_processed_line = _processed_line.erase(_start+_text.get_start(),_end-_start-_text.get_start()).insert(_start+_text.get_start(),"[url=%s]%s[/url]" % [url,_text.get_string(1)])
|
||||
if title_result and title:
|
||||
_processed_line = _processed_line.insert(_start+_text.get_start()+12+url.length()+_text.get_string(1).length(),"[/hint]").insert(_start+_text.get_start(),"[hint=%s]"%title)
|
||||
_debug("... hyperlink: "+result.get_string())
|
||||
break
|
||||
if not found_proper_match:
|
||||
break
|
||||
|
||||
while true:
|
||||
regex.compile("\\<(.*?)\\>")
|
||||
var result = regex.search(_processed_line)
|
||||
if result:
|
||||
var _start = result.get_start()
|
||||
var _end = result.get_end()
|
||||
var url = result.get_string(1)
|
||||
regex.compile("^\\s*?([^\\s]+\\@[^\\s]+\\.[^\\s]+)\\s*?$")
|
||||
var mail = regex.search(result.get_string(1))
|
||||
if mail:
|
||||
url = mail.get_string(1)
|
||||
url = _escape_chars(url)
|
||||
if mail:
|
||||
_processed_line = _processed_line.erase(_start,_end-_start).insert(_start,"[url=mailto:%s]%s[/url]"%[url,url])
|
||||
_debug("... mail link: "+result.get_string())
|
||||
else:
|
||||
_processed_line = _processed_line.erase(_start,_end-_start).insert(_start,"[url]%s[/url]"%url)
|
||||
_debug("... explicit link: "+result.get_string())
|
||||
|
||||
else:
|
||||
break
|
||||
|
||||
# Bold text
|
||||
regex.compile("(\\*\\*|\\_\\_)(.+?)\\1")
|
||||
while true:
|
||||
var result = regex.search(_processed_line)
|
||||
if not result:
|
||||
break
|
||||
var _start = result.get_start()
|
||||
var _end = result.get_end()
|
||||
_processed_line = _processed_line.erase(_start,2).insert(_start,"[b]")
|
||||
_processed_line = _processed_line.erase(_end-1,2).insert(_end-1,"[/b]")
|
||||
_debug("... bold text: "+result.get_string(2))
|
||||
|
||||
# Italic text
|
||||
while true:
|
||||
regex.compile("(\\*|_)(.+?)\\1")
|
||||
var result = regex.search(_processed_line)
|
||||
if not result:
|
||||
break
|
||||
var _start = result.get_start()
|
||||
var _end = result.get_end()
|
||||
# Sanitize nested bold+italics (Godot-specific, b and i tags must not be intertwined):
|
||||
var result_string := result.get_string(2)
|
||||
var open_b := false
|
||||
var close_b := false
|
||||
if result_string.begins_with("[b]") and result_string.find("[/b]")==-1:
|
||||
open_b = true
|
||||
elif result_string.ends_with("[/b]") and result_string.find("[b]")==-1:
|
||||
close_b = true
|
||||
if open_b:
|
||||
_processed_line = _processed_line.erase(_start,4).insert(_start,"[b][i]")
|
||||
_processed_line = _processed_line.erase(_end-2,1).insert(_end-2,"[/i]")
|
||||
elif close_b:
|
||||
_processed_line = _processed_line.erase(_start,1).insert(_start,"[i]")
|
||||
_processed_line = _processed_line.erase(_end-3,5).insert(_end-3,"[/i][/b]")
|
||||
else:
|
||||
_processed_line = _processed_line.erase(_start,1).insert(_start,"[i]")
|
||||
_processed_line = _processed_line.erase(_end+1,1).insert(_end+1,"[/i]")
|
||||
|
||||
_debug("... italic text: "+result.get_string(2))
|
||||
|
||||
# Strike-through text
|
||||
regex.compile("(\\~\\~)(.+?)\\1")
|
||||
while true:
|
||||
var result = regex.search(_processed_line)
|
||||
if result:
|
||||
#_debug(result.get_string())
|
||||
var _start = result.get_start()
|
||||
_processed_line = _processed_line.erase(_start,2).insert(_start,"[s]")
|
||||
var _end = result.get_end()
|
||||
_processed_line = _processed_line.erase(_end-1,2).insert(_end-1,"[/s]")
|
||||
_debug("... strike-through text: "+result.get_string(2))
|
||||
else:
|
||||
break
|
||||
|
||||
# Headers
|
||||
regex.compile("^#+\\s*[^\\s].*")
|
||||
while true:
|
||||
var result = regex.search(_processed_line)
|
||||
if result:
|
||||
var n := 0
|
||||
for _char in result.get_string():
|
||||
if _char!="#" or n==6:
|
||||
break
|
||||
n+=1
|
||||
var n_spaces := 0
|
||||
for _char in result.get_string().substr(n):
|
||||
if _char!=" ":
|
||||
break
|
||||
n_spaces+=1
|
||||
var header_format: Resource = _get_header_format(n)
|
||||
var n_digits := str(header_format.font_size).length()
|
||||
var _start := result.get_start()
|
||||
var opening_tags := _get_header_tags(header_format)
|
||||
_processed_line = _processed_line.erase(_start,n+n_spaces).insert(_start,opening_tags)
|
||||
var _end := result.get_end()
|
||||
_processed_line = _processed_line.insert(_end-(n+n_spaces)+opening_tags.length(),_get_header_tags(header_format,true))
|
||||
_debug("... header level %d"%n)
|
||||
else:
|
||||
break
|
||||
|
||||
# Re-insert escaped characters:
|
||||
_processed_line = _reset_escaped_chars(_processed_line)
|
||||
|
||||
_converted_text += _processed_line
|
||||
# end for line loop
|
||||
# Close any remaining open list:
|
||||
_debug("... end of text, closing all opened lists")
|
||||
for i in range(_indent_level,-1,-1):
|
||||
_converted_text += "[/%s]"%indent_types[i]
|
||||
# Close any remaining open tables:
|
||||
_debug("... end of text, closing all opened tables")
|
||||
if _within_table:
|
||||
_converted_text += "\n[/table]"
|
||||
|
||||
_debug("** ORIGINAL:")
|
||||
_debug(source_text)
|
||||
_debug(_converted_text)
|
||||
return _converted_text
|
||||
|
||||
|
||||
func _process_list_syntax(line: String, indent_spaces: Array, indent_types: Array) -> String:
|
||||
var processed_line := ""
|
||||
if line.length() == 0 and _indent_level >= 0:
|
||||
for i in range(_indent_level,-1,-1):
|
||||
_converted_text += "[/%s]" % indent_types[_indent_level]
|
||||
_indent_level-=1
|
||||
indent_spaces.pop_back()
|
||||
indent_types.pop_back()
|
||||
_converted_text += "\n"
|
||||
_debug("... empty line, closing all list tags")
|
||||
return ""
|
||||
if _indent_level == -1:
|
||||
if line.length() > 2 and line[0] in "-*+" and line[1]==" ":
|
||||
_indent_level = 0
|
||||
indent_spaces.append(0)
|
||||
indent_types.append("ul")
|
||||
_converted_text += "[ul]"
|
||||
processed_line = line.substr(2)
|
||||
_debug("... opening unordered list at level 0")
|
||||
elif line.length() > 3 and line[0] == "1" and line[1]=="." and line[2]==" ":
|
||||
_indent_level = 0
|
||||
indent_spaces.append(0)
|
||||
indent_types.append("ol")
|
||||
_converted_text += "[ol]"
|
||||
processed_line = line.substr(3)
|
||||
_debug("... opening ordered list at level 0")
|
||||
else:
|
||||
processed_line = line
|
||||
return processed_line
|
||||
var n_s := 0
|
||||
for _char in line:
|
||||
if _char == " " or _char == "\t":
|
||||
n_s += 1
|
||||
continue
|
||||
elif _char in "-*+":
|
||||
if line.length() > n_s+2 and line[n_s+1] == " ":
|
||||
if n_s == indent_spaces[_indent_level]:
|
||||
processed_line = line.substr(n_s+2)
|
||||
_debug("... adding list element at level %d"%_indent_level)
|
||||
break
|
||||
elif n_s > indent_spaces[_indent_level]:
|
||||
_indent_level += 1
|
||||
indent_spaces.append(n_s)
|
||||
indent_types.append("ul")
|
||||
_converted_text += "[ul]"
|
||||
processed_line = line.substr(n_s+2)
|
||||
_debug("... opening list at level %d and adding element"%_indent_level)
|
||||
break
|
||||
else:
|
||||
for i in range(_indent_level,-1,-1):
|
||||
if n_s < indent_spaces[i]:
|
||||
_converted_text += "[/%s]"%indent_types[_indent_level]
|
||||
_indent_level -= 1
|
||||
indent_spaces.pop_back()
|
||||
indent_types.pop_back()
|
||||
else:
|
||||
break
|
||||
_converted_text += "\n"
|
||||
processed_line = line.substr(n_s+2)
|
||||
_debug("...closing lists down to level %d and adding element"%_indent_level)
|
||||
break
|
||||
elif _char in "123456789":
|
||||
if line.length() > n_s+3 and line[n_s+1] == "." and line[n_s+2] == " ":
|
||||
if n_s == indent_spaces[_indent_level]:
|
||||
processed_line = line.substr(n_s+3)
|
||||
_debug("... adding list element at level %d"%_indent_level)
|
||||
break
|
||||
elif n_s > indent_spaces[_indent_level]:
|
||||
_indent_level += 1
|
||||
indent_spaces.append(n_s)
|
||||
indent_types.append("ol")
|
||||
_converted_text += "[ol]"
|
||||
processed_line = line.substr(n_s+3)
|
||||
_debug("... opening list at level %d and adding element"%_indent_level)
|
||||
break
|
||||
else:
|
||||
for i in range(_indent_level,-1,-1):
|
||||
if n_s < indent_spaces[i]:
|
||||
_converted_text += "[/%s]"%indent_types[_indent_level]
|
||||
_indent_level -= 1
|
||||
indent_spaces.pop_back()
|
||||
indent_types.pop_back()
|
||||
else:
|
||||
break
|
||||
_converted_text += "\n"
|
||||
processed_line = line.substr(n_s+3)
|
||||
_debug("...closing lists down to level %d and adding element"%_indent_level)
|
||||
break
|
||||
#end for _char loop
|
||||
if processed_line.is_empty():
|
||||
for i in range(_indent_level,-1,-1):
|
||||
_converted_text += "[/%s]"%indent_types[i]
|
||||
_indent_level -= 1
|
||||
indent_spaces.pop_back()
|
||||
indent_types.pop_back()
|
||||
_converted_text += "\n"
|
||||
processed_line = line
|
||||
_debug("... regular line, closing all opened lists")
|
||||
return processed_line
|
||||
|
||||
func _escape_bbcode(source: String) -> String:
|
||||
return source.replacen("[",_ESCAPE_PLACEHOLDER).replacen("]","[rb]").replacen(_ESCAPE_PLACEHOLDER,"[lb]")
|
||||
|
||||
func _escape_chars(_text: String) -> String:
|
||||
var escaped_text = _text
|
||||
for _char in _ESCAPEABLE_CHARACTERS:
|
||||
if not _char in _escaped_characters_map:
|
||||
_escaped_characters_map[_char] = _escaped_characters_map.size()
|
||||
escaped_text = escaped_text.replacen(_char,_ESCAPE_PLACEHOLDER % _escaped_characters_map[_char])
|
||||
return escaped_text
|
||||
|
||||
func _reset_escaped_chars(_text: String,code:=false) -> String:
|
||||
var unescaped_text := _text
|
||||
for _char in _ESCAPEABLE_CHARACTERS:
|
||||
if not _char in _escaped_characters_map:
|
||||
continue
|
||||
unescaped_text = unescaped_text.replacen(_ESCAPE_PLACEHOLDER%_escaped_characters_map[_char],"\\"+_char if code else _char)
|
||||
return unescaped_text
|
||||
|
||||
func _debug(string: String):
|
||||
if not _debug_mode:
|
||||
return
|
||||
print(string)
|
||||
|
||||
func _denotes_fenced_code_block(line: String, character: String) -> bool:
|
||||
var stripped_line := line.strip_edges()
|
||||
var count := stripped_line.count(character)
|
||||
if count >= 3 and count==stripped_line.length():
|
||||
return true
|
||||
else:
|
||||
return false
|
||||
|
||||
func _process_table_syntax(line: String) -> String:
|
||||
if line.count("|") < 2:
|
||||
if _within_table:
|
||||
_debug ("... end of table")
|
||||
_within_table = false
|
||||
return "\n[/table]\n"+line
|
||||
else:
|
||||
return line
|
||||
_debug("... table row: "+line)
|
||||
_table_row += 1
|
||||
var split_line := line.trim_prefix("|").trim_suffix("|").split("|")
|
||||
var processed_line := ""
|
||||
if not _within_table:
|
||||
processed_line += "[table=%d]\n" % split_line.size()
|
||||
_within_table = true
|
||||
elif _table_row == 1:
|
||||
# Handle delimiter row
|
||||
var is_delimiter := true
|
||||
for cell in split_line:
|
||||
var stripped_cell := cell.strip_edges()
|
||||
if stripped_cell.count("-")+stripped_cell.count(":") != stripped_cell.length():
|
||||
is_delimiter = false
|
||||
break
|
||||
if is_delimiter:
|
||||
_line_break = false
|
||||
return ""
|
||||
for cell in split_line:
|
||||
processed_line += "[cell]%s[/cell]" % cell.strip_edges()
|
||||
return processed_line
|
||||
|
||||
func _get_header_format(level: int) -> Resource:
|
||||
match level:
|
||||
1:
|
||||
return h1
|
||||
2:
|
||||
return h2
|
||||
3:
|
||||
return h3
|
||||
4:
|
||||
return h4
|
||||
5:
|
||||
return h5
|
||||
6:
|
||||
return h6
|
||||
push_warning("Invalid header level: "+str(level))
|
||||
return null
|
||||
|
||||
func _get_header_tags(header_format: Resource, closing := false) -> String:
|
||||
if not header_format:
|
||||
return ""
|
||||
var tags: String = ""
|
||||
if closing:
|
||||
if header_format.is_underlined:
|
||||
tags += "[/u]"
|
||||
if header_format.is_italic:
|
||||
tags += "[/i]"
|
||||
if header_format.is_bold:
|
||||
tags += "[/b]"
|
||||
if header_format.font_size:
|
||||
tags += "[/font_size]"
|
||||
else:
|
||||
if header_format.font_size:
|
||||
tags += "[font_size=%d]" % int(header_format.font_size * self.get_theme_font_size("normal_font_size"))
|
||||
if header_format.is_bold:
|
||||
tags += "[b]"
|
||||
if header_format.is_italic:
|
||||
tags += "[i]"
|
||||
if header_format.is_underlined:
|
||||
tags += "[u]"
|
||||
return tags
|
7
addons/markdownlabel/plugin.cfg
Normal file
7
addons/markdownlabel/plugin.cfg
Normal file
|
@ -0,0 +1,7 @@
|
|||
[plugin]
|
||||
|
||||
name="MarkdownLabel"
|
||||
description="A custom node that extends RichTextLabel to use Markdown instead of BBCode."
|
||||
author="Daenvil"
|
||||
version="0.9.0"
|
||||
script="plugin.gd"
|
11
addons/markdownlabel/plugin.gd
Normal file
11
addons/markdownlabel/plugin.gd
Normal file
|
@ -0,0 +1,11 @@
|
|||
@tool
|
||||
extends EditorPlugin
|
||||
|
||||
func _enter_tree():
|
||||
# Initialization of the plugin goes here.
|
||||
# Add the new type with a name, a parent type, a script and an icon.
|
||||
add_custom_type("MarkdownLabel", "RichTextLabel", preload("markdownlabel.gd"), preload("icon.svg"))
|
||||
|
||||
|
||||
func _exit_tree():
|
||||
remove_custom_type("MarkdownLabel")
|
Loading…
Add table
Add a link
Reference in a new issue