view.py 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. from dataclasses import dataclass
  2. from datetime import datetime
  3. from typing import Any, Optional, List
  4. import lc.config as c
  5. class View:
  6. pass
  7. @dataclass
  8. class Pagination(View):
  9. current: int
  10. last: int
  11. def previous(self) -> Optional[dict]:
  12. if self.current > 1:
  13. return {"page": self.current - 1}
  14. return None
  15. def next(self) -> Optional[dict]:
  16. if self.current < self.last:
  17. return {"page": self.current + 1}
  18. return None
  19. @classmethod
  20. def from_total(cls, current, total) -> "Pagination":
  21. return cls(current=current, last=((total - 1) // c.app.per_page) + 1,)
  22. @dataclass
  23. class UserInvite(View):
  24. claimed: bool
  25. claimant: str
  26. token: str
  27. @dataclass
  28. class AddUser(View):
  29. token: str
  30. @dataclass
  31. class AdminPane(View):
  32. invites: List[UserInvite]
  33. @dataclass
  34. class Config(View):
  35. username: str
  36. admin_pane: Optional[AdminPane]
  37. msg: Optional[int] = None
  38. def bookmarklet_link(self):
  39. return (
  40. "javascript:(function(){window.open(`"
  41. + c.app.config.app_path
  42. + "/u/"
  43. + self.username
  44. + "/l?name=${document.title}&url=${document.URL}`);})();"
  45. )
  46. def message(self) -> Optional[str]:
  47. if self.msg == 1:
  48. return "Password changed."
  49. elif self.msg == 2:
  50. return "Mismatched new passwords; please try again."
  51. elif self.msg == 3:
  52. return "Incorrect old password; please try again."
  53. return None
  54. @dataclass
  55. class Tag(View):
  56. url: str
  57. name: str
  58. @dataclass
  59. class HierTagList:
  60. user: str
  61. tags: List[Tag]
  62. def render(self) -> str:
  63. groups: dict = {}
  64. for tag in (t.name for t in self.tags):
  65. if "/" not in tag:
  66. groups[tag] = groups.get(tag, {})
  67. else:
  68. chunks = tag.split("/")
  69. focus = groups[chunks[0]] = groups.get(chunks[0], {})
  70. for c in chunks[1:]:
  71. focus[c] = focus = focus.get(c, {})
  72. return "\n".join(self.render_html(k, v) for k, v in groups.items())
  73. def render_html(self, prefix: str, values: dict) -> str:
  74. link = self._render_html(prefix, values, [])
  75. return f'<span class="tag">{link}</span>'
  76. def _href(self, tag: str, init: List[str]) -> str:
  77. link = "/".join(init + [tag])
  78. return f'<a href="/u/{self.user}/t/{link}">{tag}</a>'
  79. def _render_html(self, prefix: str, values: dict, init: List[str]) -> str:
  80. if not values:
  81. return self._href(prefix, init)
  82. if len(values) == 1:
  83. k, v = values.popitem()
  84. rest = self._render_html(k, v, init + [prefix])
  85. prefix_href = self._href(prefix, init)
  86. return f"{prefix_href}/{rest}"
  87. else:
  88. fragments = []
  89. for k, v in values.items():
  90. fragments.append(self._render_html(k, v, init + [prefix]))
  91. items = ", ".join(fragments)
  92. prefix_href = self._href(prefix, init)
  93. return f"{prefix_href}/{{{items}}}"
  94. @dataclass
  95. class Link(View):
  96. id: int
  97. url: str
  98. name: str
  99. description: str
  100. private: bool
  101. tags: List[Tag]
  102. created: datetime
  103. is_mine: bool
  104. link_url: str
  105. user: str
  106. def hier_tags(self) -> str:
  107. return HierTagList(user=self.user, tags=self.tags).render()
  108. @dataclass
  109. class LinkList(View):
  110. links: List[Any]
  111. tags: List[Tag]
  112. user: str
  113. pages: Optional[Pagination] = None
  114. def hier_tags(self) -> str:
  115. return HierTagList(user=self.user, tags=self.tags).render()
  116. @dataclass
  117. class SingleLink(View):
  118. link: Any
  119. @dataclass
  120. class Message(View):
  121. title: str
  122. message: str
  123. @dataclass
  124. class AddLinkDefaults(View):
  125. url: Optional[str] = None
  126. name: Optional[str] = None
  127. @dataclass
  128. class Page(View):
  129. title: str
  130. content: str
  131. user: Optional[Any]
  132. @dataclass
  133. class Error(View):
  134. code: int
  135. message: str