spoiler.py 2.4 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. import re
  2. from xml.etree.ElementTree import SubElement
  3. import markdown
  4. from django.utils.crypto import get_random_string
  5. from markdown.blockprocessors import BlockProcessor
  6. from markdown.preprocessors import Preprocessor
  7. SPOILER_START = get_random_string(32)
  8. SPOILER_END = get_random_string(32)
  9. class SpoilerExtension(markdown.Extension):
  10. def extendMarkdown(self, md):
  11. md.registerExtension(self)
  12. md.preprocessors.register(SpoilerPreprocessor(md), "misago_bbcode_spoiler", 200)
  13. md.parser.blockprocessors.register(
  14. SpoilerBlockProcessor(md.parser), "misago_bbcode_spoiler", 85
  15. )
  16. class SpoilerPreprocessor(Preprocessor):
  17. SPOILER_BLOCK_RE = re.compile(
  18. r"""
  19. \[spoiler\](?P<text>.*?)\[/spoiler\]
  20. """.strip(),
  21. re.IGNORECASE | re.MULTILINE | re.DOTALL,
  22. )
  23. def run(self, lines):
  24. text = "\n".join(lines)
  25. while self.SPOILER_BLOCK_RE.search(text):
  26. text = self.SPOILER_BLOCK_RE.sub(self.replace, text)
  27. return text.split("\n")
  28. def replace(self, matchobj):
  29. text = matchobj.group("text")
  30. return "\n\n%s\n\n%s\n\n%s\n\n" % (SPOILER_START, text, SPOILER_END)
  31. class SpoilerBlockProcessor(BlockProcessor):
  32. def __init__(self, *args, **kwargs):
  33. super().__init__(*args, **kwargs)
  34. self._spoiler = 0
  35. self._children = []
  36. def test(self, parent, block):
  37. return block.strip().startswith(SPOILER_START) or self._spoiler
  38. def run(self, parent, blocks):
  39. block = blocks.pop(0)
  40. if block.strip().startswith(SPOILER_START):
  41. self._spoiler += 1
  42. self._children.append(block)
  43. if block.strip() == SPOILER_END:
  44. self._spoiler -= 1
  45. if not self._spoiler:
  46. children, self._children = self._children[1:-1], []
  47. aside = SubElement(parent, "aside")
  48. aside.set("class", "spoiler-block")
  49. blockquote = SubElement(aside, "blockquote")
  50. blockquote.set("class", "spoiler-body")
  51. blockquote.set("data-block", "spoiler")
  52. overlay = SubElement(aside, "div")
  53. overlay.set("class", "spoiler-overlay")
  54. overlay.set("data-noquote", "1")
  55. reveal_btn = SubElement(overlay, "button")
  56. reveal_btn.set("class", "spoiler-reveal")
  57. reveal_btn.set("type", "button")
  58. self.parser.parseBlocks(blockquote, children)