Skip to content

Commit 3efb54c

Browse files
authored
Merge pull request #266 from Alihtt/feature-markdown-add-id-extension
Feature markdown add id extension
2 parents 99eef77 + 62f3e6b commit 3efb54c

File tree

6 files changed

+71
-10
lines changed

6 files changed

+71
-10
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
__pycache__
22
build/
33
dist/
4+
venv/
45
*.egg-info/
56
*.pypirc
67
*.pyc

Dockerfile

+17-10
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,27 @@ FROM alpine:3.16.0
22

33
WORKDIR /app
44

5-
RUN set -xe;
5+
# Install required packages including tini
6+
RUN set -xe && \
7+
apk add --no-cache python3 py3-pip tini && \
8+
pip install --upgrade pip setuptools-scm
69

10+
# Copy project files
711
COPY . .
812

9-
RUN apk add --no-cache python3 py3-pip tini; \
10-
pip install --upgrade pip setuptools-scm; \
11-
python3 setup.py install; \
12-
python3 martor_demo/manage.py makemigrations; \
13-
python3 martor_demo/manage.py migrate; \
14-
addgroup -g 1000 appuser; \
15-
adduser -u 1000 -G appuser -D -h /app appuser; \
13+
# Install Python dependencies and setup Django app
14+
RUN python3 setup.py install && \
15+
python3 martor_demo/manage.py makemigrations && \
16+
python3 martor_demo/manage.py migrate
17+
18+
# Create user and set permissions
19+
RUN addgroup -g 1000 appuser && \
20+
adduser -u 1000 -G appuser -D -h /app appuser && \
1621
chown -R appuser:appuser /app
1722

1823
USER appuser
1924
EXPOSE 8000/tcp
20-
ENTRYPOINT [ "tini", "--" ]
21-
CMD [ "python3", "/app/martor_demo/manage.py", "runserver", "0.0.0.0:8000" ]
25+
26+
# Use full path for tini
27+
ENTRYPOINT ["/sbin/tini", "--"]
28+
CMD ["python3", "/app/martor_demo/manage.py", "runserver", "0.0.0.0:8000"]

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
* Supports Django Admin
2121
* Toolbar Buttons
2222
* Highlight `pre`
23+
* Custom ID Attributes (Add custom IDs to any text element using `{#custom-id}` syntax, e.g., `# Heading1 {#my-h1-id}`, for easy linking and navigation.
2324

2425

2526
### Preview
@@ -140,6 +141,7 @@ MARTOR_MARKDOWN_EXTENSIONS = [
140141
'martor.extensions.emoji', # to parse markdown emoji
141142
'martor.extensions.mdx_video', # to parse embed/iframe video
142143
'martor.extensions.escape_html', # to handle the XSS vulnerabilities
144+
"martor.extensions.mdx_add_id", # to parse id like {#this_is_id}
143145
]
144146

145147
# Markdown Extensions Configs

martor/extensions/mdx_add_id.py

+37
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import markdown
2+
from xml.etree import ElementTree
3+
4+
# Regex pattern to detect `{#id_name}` at the end of the line
5+
ADD_ID_RE = r"(.+?)\s\{#([a-zA-Z0-9_-]+)\}$"
6+
7+
8+
class AddIDPattern(markdown.inlinepatterns.Pattern):
9+
"""Pattern to match Markdown text ending with `{#id}` and set it as an ID."""
10+
11+
def handleMatch(self, m):
12+
text_content = m.group(2).strip() # Actual text content
13+
id_value = m.group(3) # The ID inside `{#id}`
14+
15+
# Create a <span> element to hold the text and ID
16+
el = ElementTree.Element("span")
17+
el.text = markdown.util.AtomicString(text_content)
18+
el.set("id", id_value)
19+
return el
20+
21+
22+
class AddIDExtension(markdown.Extension):
23+
"""Add ID Extension for Python-Markdown."""
24+
25+
def extendMarkdown(self, md: markdown.core.Markdown, *args):
26+
"""Register AddIDPattern with the Markdown parser."""
27+
md.inlinePatterns.register(AddIDPattern(ADD_ID_RE, md), "add_id", 9)
28+
29+
30+
def makeExtension(*args, **kwargs):
31+
return AddIDExtension(*args, **kwargs)
32+
33+
34+
if __name__ == "__main__":
35+
import doctest
36+
37+
doctest.testmod()

martor/settings.py

+1
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@
7777
"martor.extensions.emoji", # to parse markdown emoji
7878
"martor.extensions.mdx_video", # to parse embed/iframe video
7979
"martor.extensions.escape_html", # to handle the XSS vulnerabilities
80+
"martor.extensions.mdx_add_id", # to parse id like {#this_is_id}
8081
],
8182
)
8283

martor/tests/tests.py

+13
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,19 @@ def test_markdownify(self):
110110
# f'<p><a class="direct-mention-link" href="https://python.web.id/author/{self.user.username}/">{self.user.username}</a></p>',
111111
# )
112112

113+
# Id
114+
response = self.client.post(
115+
"/martor/markdownify/",
116+
{
117+
"content": "__Advertisement :)__ {#ad-section}\n###### h6 Heading {#h6-heading}"
118+
},
119+
)
120+
self.assertEqual(response.status_code, 200)
121+
self.assertEqual(
122+
response.content.decode("utf-8"),
123+
'<p><span id="ad-section"><strong>Advertisement :)</strong></span></p>\n<h6><span id="h6-heading">h6 Heading</span></h6>',
124+
) # noqa: E501
125+
113126
def test_markdownify_xss_handled(self):
114127
xss_payload_1 = "[aaaa](javascript:alert(1))"
115128
response_1 = markdownify(xss_payload_1)

0 commit comments

Comments
 (0)