Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature markdown add id extension #266

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
__pycache__
build/
dist/
venv/
*.egg-info/
*.pypirc
*.pyc
Expand Down
27 changes: 17 additions & 10 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,27 @@ FROM alpine:3.16.0

WORKDIR /app

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

# Copy project files
COPY . .

RUN apk add --no-cache python3 py3-pip tini; \
pip install --upgrade pip setuptools-scm; \
python3 setup.py install; \
python3 martor_demo/manage.py makemigrations; \
python3 martor_demo/manage.py migrate; \
addgroup -g 1000 appuser; \
adduser -u 1000 -G appuser -D -h /app appuser; \
# Install Python dependencies and setup Django app
RUN python3 setup.py install && \
python3 martor_demo/manage.py makemigrations && \
python3 martor_demo/manage.py migrate

# Create user and set permissions
RUN addgroup -g 1000 appuser && \
adduser -u 1000 -G appuser -D -h /app appuser && \
chown -R appuser:appuser /app

USER appuser
EXPOSE 8000/tcp
ENTRYPOINT [ "tini", "--" ]
CMD [ "python3", "/app/martor_demo/manage.py", "runserver", "0.0.0.0:8000" ]

# Use full path for tini
ENTRYPOINT ["/sbin/tini", "--"]
CMD ["python3", "/app/martor_demo/manage.py", "runserver", "0.0.0.0:8000"]
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
* Supports Django Admin
* Toolbar Buttons
* Highlight `pre`
* 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.


### Preview
Expand Down Expand Up @@ -140,6 +141,7 @@ MARTOR_MARKDOWN_EXTENSIONS = [
'martor.extensions.emoji', # to parse markdown emoji
'martor.extensions.mdx_video', # to parse embed/iframe video
'martor.extensions.escape_html', # to handle the XSS vulnerabilities
"martor.extensions.mdx_add_id", # to parse id like {#this_is_id}
]

# Markdown Extensions Configs
Expand Down
37 changes: 37 additions & 0 deletions martor/extensions/mdx_add_id.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import markdown
from xml.etree import ElementTree

# Regex pattern to detect `{#id_name}` at the end of the line
ADD_ID_RE = r"(.+?)\s\{#([a-zA-Z0-9_-]+)\}$"


class AddIDPattern(markdown.inlinepatterns.Pattern):
"""Pattern to match Markdown text ending with `{#id}` and set it as an ID."""

def handleMatch(self, m):
text_content = m.group(2).strip() # Actual text content
id_value = m.group(3) # The ID inside `{#id}`

# Create a <span> element to hold the text and ID
el = ElementTree.Element("span")
el.text = markdown.util.AtomicString(text_content)
el.set("id", id_value)
return el


class AddIDExtension(markdown.Extension):
"""Add ID Extension for Python-Markdown."""

def extendMarkdown(self, md: markdown.core.Markdown, *args):
"""Register AddIDPattern with the Markdown parser."""
md.inlinePatterns.register(AddIDPattern(ADD_ID_RE, md), "add_id", 9)


def makeExtension(*args, **kwargs):
return AddIDExtension(*args, **kwargs)


if __name__ == "__main__":
import doctest

doctest.testmod()
1 change: 1 addition & 0 deletions martor/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"martor.extensions.emoji", # to parse markdown emoji
"martor.extensions.mdx_video", # to parse embed/iframe video
"martor.extensions.escape_html", # to handle the XSS vulnerabilities
"martor.extensions.mdx_add_id", # to parse id like {#this_is_id}
],
)

Expand Down
13 changes: 13 additions & 0 deletions martor/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,19 @@ def test_markdownify(self):
# f'<p><a class="direct-mention-link" href="https://python.web.id/author/{self.user.username}/">{self.user.username}</a></p>',
# )

# Id
response = self.client.post(
"/martor/markdownify/",
{
"content": "__Advertisement :)__ {#ad-section}\n###### h6 Heading {#h6-heading}"
},
)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response.content.decode("utf-8"),
'<p><span id="ad-section"><strong>Advertisement :)</strong></span></p>\n<h6><span id="h6-heading">h6 Heading</span></h6>',
) # noqa: E501

def test_markdownify_xss_handled(self):
xss_payload_1 = "[aaaa](javascript:alert(1))"
response_1 = markdownify(xss_payload_1)
Expand Down
Loading