read_more.py 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. """
  2. Read More
  3. =========
  4. This plugin inserts an inline "Read More" link into summary's last HTML element.
  5. """
  6. import logging
  7. from pelican import contents, signals
  8. from pelican.generators import ArticlesGenerator
  9. from pelican.utils import truncate_html_words
  10. try:
  11. from lxml.etree import ParserError
  12. from lxml.html import fragment_fromstring, fragments_fromstring, tostring
  13. except ImportError:
  14. raise Exception("Unable to find lxml. Read More requires lxml to be installed.")
  15. logger = logging.getLogger(__name__)
  16. def insert_into_last_element(html, element):
  17. """
  18. Function to insert an HTML element into another HTML fragment.
  19. example:
  20. html = '<p>paragraph1</p><p>paragraph2...</p>'
  21. element = '<a href="/read-more/">read more</a>'
  22. ---> '<p>paragraph1</p><p>paragraph2...<a href="/read-more/">read more</a></p>'
  23. """
  24. try:
  25. item = fragment_fromstring(element)
  26. except (ParserError, TypeError):
  27. item = fragment_fromstring("<span></span>")
  28. try:
  29. doc = fragments_fromstring(html)
  30. doc[-1].append(item)
  31. return b"".join(tostring(e) for e in doc).decode()
  32. except (ParserError, TypeError):
  33. return ""
  34. def insert_read_more_link(instance):
  35. """
  36. Insert an inline "Read More" link into the last element of the summary
  37. :param instance:
  38. :return:
  39. """
  40. # only deals with Article type
  41. if type(instance) != contents.Article:
  42. logger.info("[read_more] Skipped: Content is not of type Article")
  43. return
  44. SUMMARY_END_SUFFIX = instance.settings.get("SUMMARY_END_SUFFIX", "...")
  45. SUMMARY_MAX_LENGTH = instance.settings.get("SUMMARY_MAX_LENGTH", 50)
  46. READ_MORE_LINK = instance.settings.get("READ_MORE_LINK", "<span>continue</span>")
  47. READ_MORE_LINK_FORMAT = instance.settings.get(
  48. "READ_MORE_LINK_FORMAT", '<a class="read-more" href="/{url}">{text}</a>'
  49. )
  50. logger.debug(f"[read_more] SUMMARY_MAX_LENGTH: {SUMMARY_MAX_LENGTH}")
  51. logger.debug(f"[read_more] READ_MORE_LINK: {READ_MORE_LINK}")
  52. logger.debug(f"[read_more] READ_MORE_LINK_FORMAT: {READ_MORE_LINK_FORMAT}")
  53. if not (SUMMARY_MAX_LENGTH and READ_MORE_LINK and READ_MORE_LINK_FORMAT):
  54. logger.error("[read_more] Abort: Settings not present")
  55. return
  56. summary = truncate_html_words(
  57. instance.content, SUMMARY_MAX_LENGTH, SUMMARY_END_SUFFIX
  58. )
  59. if not summary:
  60. logger.error(
  61. f"[read_more] Error: Summary truncation failed (None) -> \
  62. {instance.source_path}"
  63. )
  64. return
  65. if summary != instance.content:
  66. read_more_link = READ_MORE_LINK_FORMAT.format(
  67. url=instance.url, text=READ_MORE_LINK
  68. )
  69. logger.debug(f"[read_more] Format: {read_more_link}")
  70. logger.debug(f"[read_more] Summary (before inject): {summary}")
  71. result = insert_into_last_element(summary, read_more_link)
  72. if result:
  73. instance.metadata["summary"] = result
  74. logger.debug(
  75. f"[read_more] Summary (after inject): {instance.metadata['summary']}"
  76. )
  77. else:
  78. logger.error("[read_more] Error: Link not added to summary")
  79. def run_plugin(generators):
  80. for generator in generators:
  81. if isinstance(generator, ArticlesGenerator):
  82. for article in generator.articles:
  83. insert_read_more_link(article)
  84. def register():
  85. try:
  86. signals.all_generators_finalized.connect(run_plugin)
  87. except AttributeError:
  88. # NOTE: This may result in #314 so shouldn't really be relied on
  89. # https://github.com/getpelican/pelican-plugins/issues/314
  90. signals.content_object_init.connect(insert_read_more_link)