wagtail_hooks.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. from django.contrib.auth.models import Permission
  2. from django.urls import reverse
  3. from django.utils.http import urlencode
  4. from django.utils.translation import gettext
  5. from django.utils.translation import gettext_lazy as _
  6. from draftjs_exporter.dom import DOM
  7. import wagtail.admin.rich_text.editors.draftail.features as draftail_features
  8. from wagtail.admin.auth import user_has_any_page_permission
  9. from wagtail.admin.localization import get_available_admin_languages, get_available_admin_time_zones
  10. from wagtail.admin.menu import MenuItem, SubmenuMenuItem, reports_menu, settings_menu
  11. from wagtail.admin.navigation import get_explorable_root_page
  12. from wagtail.admin.rich_text import (
  13. HalloFormatPlugin, HalloHeadingPlugin, HalloListPlugin, HalloPlugin)
  14. from wagtail.admin.rich_text.converters.contentstate import link_entity
  15. from wagtail.admin.rich_text.converters.editor_html import (
  16. LinkTypeRule, PageLinkHandler, WhitelistRule)
  17. from wagtail.admin.rich_text.converters.html_to_contentstate import (
  18. BlockElementHandler, ExternalLinkElementHandler, HorizontalRuleHandler,
  19. InlineStyleElementHandler, ListElementHandler, ListItemElementHandler, PageLinkElementHandler)
  20. from wagtail.admin.search import SearchArea
  21. from wagtail.admin.site_summary import PagesSummaryItem
  22. from wagtail.admin.views.account import email_management_enabled, password_management_enabled
  23. from wagtail.admin.viewsets import viewsets
  24. from wagtail.admin.widgets import Button, ButtonWithDropdownFromHook, PageListingButton
  25. from wagtail.core import hooks
  26. from wagtail.core.models import UserPagePermissionsProxy
  27. from wagtail.core.permissions import (
  28. collection_permission_policy, task_permission_policy, workflow_permission_policy)
  29. from wagtail.core.whitelist import allow_without_attributes, attribute_rule, check_url
  30. class ExplorerMenuItem(MenuItem):
  31. template = 'wagtailadmin/shared/explorer_menu_item.html'
  32. def is_shown(self, request):
  33. return user_has_any_page_permission(request.user)
  34. def get_context(self, request):
  35. context = super().get_context(request)
  36. start_page = get_explorable_root_page(request.user)
  37. if start_page:
  38. context['start_page_id'] = start_page.id
  39. return context
  40. @hooks.register('register_admin_menu_item')
  41. def register_explorer_menu_item():
  42. return ExplorerMenuItem(
  43. _('Pages'), reverse('wagtailadmin_explore_root'),
  44. name='explorer',
  45. icon_name='folder-open-inverse',
  46. order=100)
  47. class SettingsMenuItem(SubmenuMenuItem):
  48. template = 'wagtailadmin/shared/menu_settings_menu_item.html'
  49. @hooks.register('register_admin_menu_item')
  50. def register_settings_menu():
  51. return SettingsMenuItem(
  52. _('Settings'),
  53. settings_menu,
  54. icon_name='cogs',
  55. order=10000)
  56. @hooks.register('register_permissions')
  57. def register_permissions():
  58. return Permission.objects.filter(content_type__app_label='wagtailadmin', codename='access_admin')
  59. class PageSearchArea(SearchArea):
  60. def __init__(self):
  61. super().__init__(
  62. _('Pages'), reverse('wagtailadmin_pages:search'),
  63. name='pages',
  64. icon_name='folder-open-inverse',
  65. order=100)
  66. def is_shown(self, request):
  67. return user_has_any_page_permission(request.user)
  68. @hooks.register('register_admin_search_area')
  69. def register_pages_search_area():
  70. return PageSearchArea()
  71. class CollectionsMenuItem(MenuItem):
  72. def is_shown(self, request):
  73. return collection_permission_policy.user_has_any_permission(
  74. request.user, ['add', 'change', 'delete']
  75. )
  76. @hooks.register('register_settings_menu_item')
  77. def register_collections_menu_item():
  78. return CollectionsMenuItem(_('Collections'), reverse('wagtailadmin_collections:index'), icon_name='folder-open-1', order=700)
  79. class WorkflowsMenuItem(MenuItem):
  80. def is_shown(self, request):
  81. return workflow_permission_policy.user_has_any_permission(
  82. request.user, ['add', 'change', 'delete']
  83. )
  84. class WorkflowTasksMenuItem(MenuItem):
  85. def is_shown(self, request):
  86. return task_permission_policy.user_has_any_permission(
  87. request.user, ['add', 'change', 'delete']
  88. )
  89. @hooks.register('register_settings_menu_item')
  90. def register_workflows_menu_item():
  91. return WorkflowsMenuItem(_('Workflows'), reverse('wagtailadmin_workflows:index'), icon_name='tasks', order=100)
  92. @hooks.register('register_settings_menu_item')
  93. def register_workflow_tasks_menu_item():
  94. return WorkflowTasksMenuItem(_('Workflow tasks'), reverse('wagtailadmin_workflows:task_index'), icon_name='thumbtack', order=150)
  95. @hooks.register('register_page_listing_buttons')
  96. def page_listing_buttons(page, page_perms, is_parent=False, next_url=None):
  97. if page_perms.can_edit():
  98. yield PageListingButton(
  99. _('Edit'),
  100. reverse('wagtailadmin_pages:edit', args=[page.id]),
  101. attrs={'aria-label': _("Edit '%(title)s'") % {'title': page.get_admin_display_title()}},
  102. priority=10
  103. )
  104. if page.has_unpublished_changes and page.is_previewable():
  105. yield PageListingButton(
  106. _('View draft'),
  107. reverse('wagtailadmin_pages:view_draft', args=[page.id]),
  108. attrs={
  109. 'aria-label': _("Preview draft version of '%(title)s'") % {'title': page.get_admin_display_title()},
  110. 'target': '_blank', 'rel': 'noopener noreferrer'
  111. },
  112. priority=20
  113. )
  114. if page.live and page.url:
  115. yield PageListingButton(
  116. _('View live'),
  117. page.url,
  118. attrs={
  119. 'target': "_blank", 'rel': 'noopener noreferrer',
  120. 'aria-label': _("View live version of '%(title)s'") % {'title': page.get_admin_display_title()},
  121. },
  122. priority=30
  123. )
  124. if page_perms.can_add_subpage():
  125. if is_parent:
  126. yield Button(
  127. _('Add child page'),
  128. reverse('wagtailadmin_pages:add_subpage', args=[page.id]),
  129. attrs={
  130. 'aria-label': _("Add a child page to '%(title)s' ") % {'title': page.get_admin_display_title()},
  131. },
  132. classes={'button', 'button-small', 'bicolor', 'icon', 'white', 'icon-plus'},
  133. priority=40
  134. )
  135. else:
  136. yield PageListingButton(
  137. _('Add child page'),
  138. reverse('wagtailadmin_pages:add_subpage', args=[page.id]),
  139. attrs={'aria-label': _("Add a child page to '%(title)s' ") % {'title': page.get_admin_display_title()}},
  140. priority=40
  141. )
  142. yield ButtonWithDropdownFromHook(
  143. _('More'),
  144. hook_name='register_page_listing_more_buttons',
  145. page=page,
  146. page_perms=page_perms,
  147. is_parent=is_parent,
  148. next_url=next_url,
  149. attrs={
  150. 'target': '_blank', 'rel': 'noopener noreferrer',
  151. 'title': _("View more options for '%(title)s'") % {'title': page.get_admin_display_title()}
  152. },
  153. priority=50
  154. )
  155. @hooks.register('register_page_listing_more_buttons')
  156. def page_listing_more_buttons(page, page_perms, is_parent=False, next_url=None):
  157. if page_perms.can_move():
  158. yield Button(
  159. _('Move'),
  160. reverse('wagtailadmin_pages:move', args=[page.id]),
  161. attrs={"title": _("Move page '%(title)s'") % {'title': page.get_admin_display_title()}},
  162. priority=10
  163. )
  164. if page_perms.can_copy():
  165. url = reverse('wagtailadmin_pages:copy', args=[page.id])
  166. if next_url:
  167. url += '?' + urlencode({'next': next_url})
  168. yield Button(
  169. _('Copy'),
  170. url,
  171. attrs={'title': _("Copy page '%(title)s'") % {'title': page.get_admin_display_title()}},
  172. priority=20
  173. )
  174. if page_perms.can_delete():
  175. url = reverse('wagtailadmin_pages:delete', args=[page.id])
  176. if next_url:
  177. url += '?' + urlencode({'next': next_url})
  178. yield Button(
  179. _('Delete'),
  180. url,
  181. attrs={'title': _("Delete page '%(title)s'") % {'title': page.get_admin_display_title()}},
  182. priority=30
  183. )
  184. if page_perms.can_unpublish():
  185. url = reverse('wagtailadmin_pages:unpublish', args=[page.id])
  186. if next_url:
  187. url += '?' + urlencode({'next': next_url})
  188. yield Button(
  189. _('Unpublish'),
  190. url,
  191. attrs={'title': _("Unpublish page '%(title)s'") % {'title': page.get_admin_display_title()}},
  192. priority=40
  193. )
  194. if page_perms.can_view_revisions():
  195. yield Button(
  196. _('History'),
  197. reverse('wagtailadmin_pages:history', args=[page.id]),
  198. attrs={'title': _("View page history for '%(title)s'") % {'title': page.get_admin_display_title()}},
  199. priority=50
  200. )
  201. @hooks.register('register_admin_urls')
  202. def register_viewsets_urls():
  203. viewsets.populate()
  204. return viewsets.get_urlpatterns()
  205. @hooks.register('register_account_menu_item')
  206. def register_account_set_profile_picture(request):
  207. return {
  208. 'url': reverse('wagtailadmin_account_change_avatar'),
  209. 'label': _('Set profile picture'),
  210. 'help_text': _("Change your profile picture.")
  211. }
  212. @hooks.register('register_account_menu_item')
  213. def register_account_change_email(request):
  214. if email_management_enabled():
  215. return {
  216. 'url': reverse('wagtailadmin_account_change_email'),
  217. 'label': _('Change email'),
  218. 'help_text': _('Change the email address linked to your account.'),
  219. }
  220. @hooks.register('register_account_menu_item')
  221. def register_account_change_password(request):
  222. if password_management_enabled() and request.user.has_usable_password():
  223. return {
  224. 'url': reverse('wagtailadmin_account_change_password'),
  225. 'label': _('Change password'),
  226. 'help_text': _('Change the password you use to log in.'),
  227. }
  228. @hooks.register('register_account_menu_item')
  229. def register_account_notification_preferences(request):
  230. user_perms = UserPagePermissionsProxy(request.user)
  231. if user_perms.can_edit_pages() or user_perms.can_publish_pages():
  232. return {
  233. 'url': reverse('wagtailadmin_account_notification_preferences'),
  234. 'label': _('Notification preferences'),
  235. 'help_text': _('Choose which email notifications to receive.'),
  236. }
  237. @hooks.register('register_account_menu_item')
  238. def register_account_preferred_language_preferences(request):
  239. if len(get_available_admin_languages()) > 1:
  240. return {
  241. 'url': reverse('wagtailadmin_account_language_preferences'),
  242. 'label': _('Language preferences'),
  243. 'help_text': _('Choose the language you want to use here.'),
  244. }
  245. @hooks.register('register_account_menu_item')
  246. def register_account_current_time_zone(request):
  247. if len(get_available_admin_time_zones()) > 1:
  248. return {
  249. 'url': reverse('wagtailadmin_account_current_time_zone'),
  250. 'label': _('Current Time Zone'),
  251. 'help_text': _('Choose your current time zone.'),
  252. }
  253. @hooks.register('register_account_menu_item')
  254. def register_account_change_name(request):
  255. return {
  256. 'url': reverse('wagtailadmin_account_change_name'),
  257. 'label': _('Change name'),
  258. 'help_text': _('Change your first and last name on your account.'),
  259. }
  260. @hooks.register('register_rich_text_features')
  261. def register_core_features(features):
  262. # Hallo.js
  263. features.register_editor_plugin(
  264. 'hallo', 'hr',
  265. HalloPlugin(
  266. name='hallohr',
  267. js=['wagtailadmin/js/hallo-plugins/hallo-hr.js'],
  268. order=45,
  269. )
  270. )
  271. features.register_converter_rule('editorhtml', 'hr', [
  272. WhitelistRule('hr', allow_without_attributes)
  273. ])
  274. features.register_editor_plugin(
  275. 'hallo', 'link',
  276. HalloPlugin(
  277. name='hallowagtaillink',
  278. js=[
  279. 'wagtailadmin/js/page-chooser-modal.js',
  280. 'wagtailadmin/js/hallo-plugins/hallo-wagtaillink.js',
  281. ],
  282. )
  283. )
  284. features.register_converter_rule('editorhtml', 'link', [
  285. WhitelistRule('a', attribute_rule({'href': check_url})),
  286. LinkTypeRule('page', PageLinkHandler),
  287. ])
  288. features.register_editor_plugin(
  289. 'hallo', 'bold', HalloFormatPlugin(format_name='bold')
  290. )
  291. features.register_converter_rule('editorhtml', 'bold', [
  292. WhitelistRule('b', allow_without_attributes),
  293. WhitelistRule('strong', allow_without_attributes),
  294. ])
  295. features.register_editor_plugin(
  296. 'hallo', 'italic', HalloFormatPlugin(format_name='italic')
  297. )
  298. features.register_converter_rule('editorhtml', 'italic', [
  299. WhitelistRule('i', allow_without_attributes),
  300. WhitelistRule('em', allow_without_attributes),
  301. ])
  302. headings_elements = ['h1', 'h2', 'h3', 'h4', 'h5', 'h6']
  303. headings_order_start = HalloHeadingPlugin.default_order + 1
  304. for order, element in enumerate(headings_elements, start=headings_order_start):
  305. features.register_editor_plugin(
  306. 'hallo', element, HalloHeadingPlugin(element=element, order=order)
  307. )
  308. features.register_converter_rule('editorhtml', element, [
  309. WhitelistRule(element, allow_without_attributes)
  310. ])
  311. features.register_editor_plugin(
  312. 'hallo', 'ol', HalloListPlugin(list_type='ordered')
  313. )
  314. features.register_converter_rule('editorhtml', 'ol', [
  315. WhitelistRule('ol', allow_without_attributes),
  316. WhitelistRule('li', allow_without_attributes),
  317. ])
  318. features.register_editor_plugin(
  319. 'hallo', 'ul', HalloListPlugin(list_type='unordered')
  320. )
  321. features.register_converter_rule('editorhtml', 'ul', [
  322. WhitelistRule('ul', allow_without_attributes),
  323. WhitelistRule('li', allow_without_attributes),
  324. ])
  325. # Draftail
  326. features.register_editor_plugin(
  327. 'draftail', 'hr', draftail_features.BooleanFeature('enableHorizontalRule')
  328. )
  329. features.register_converter_rule('contentstate', 'hr', {
  330. 'from_database_format': {
  331. 'hr': HorizontalRuleHandler(),
  332. },
  333. 'to_database_format': {
  334. 'entity_decorators': {'HORIZONTAL_RULE': lambda props: DOM.create_element('hr')}
  335. }
  336. })
  337. features.register_editor_plugin(
  338. 'draftail', 'h1', draftail_features.BlockFeature({
  339. 'label': 'H1',
  340. 'type': 'header-one',
  341. 'description': gettext('Heading %(level)d') % {'level': 1},
  342. })
  343. )
  344. features.register_converter_rule('contentstate', 'h1', {
  345. 'from_database_format': {
  346. 'h1': BlockElementHandler('header-one'),
  347. },
  348. 'to_database_format': {
  349. 'block_map': {'header-one': 'h1'}
  350. }
  351. })
  352. features.register_editor_plugin(
  353. 'draftail', 'h2', draftail_features.BlockFeature({
  354. 'label': 'H2',
  355. 'type': 'header-two',
  356. 'description': gettext('Heading %(level)d') % {'level': 2},
  357. })
  358. )
  359. features.register_converter_rule('contentstate', 'h2', {
  360. 'from_database_format': {
  361. 'h2': BlockElementHandler('header-two'),
  362. },
  363. 'to_database_format': {
  364. 'block_map': {'header-two': 'h2'}
  365. }
  366. })
  367. features.register_editor_plugin(
  368. 'draftail', 'h3', draftail_features.BlockFeature({
  369. 'label': 'H3',
  370. 'type': 'header-three',
  371. 'description': gettext('Heading %(level)d') % {'level': 3},
  372. })
  373. )
  374. features.register_converter_rule('contentstate', 'h3', {
  375. 'from_database_format': {
  376. 'h3': BlockElementHandler('header-three'),
  377. },
  378. 'to_database_format': {
  379. 'block_map': {'header-three': 'h3'}
  380. }
  381. })
  382. features.register_editor_plugin(
  383. 'draftail', 'h4', draftail_features.BlockFeature({
  384. 'label': 'H4',
  385. 'type': 'header-four',
  386. 'description': gettext('Heading %(level)d') % {'level': 4},
  387. })
  388. )
  389. features.register_converter_rule('contentstate', 'h4', {
  390. 'from_database_format': {
  391. 'h4': BlockElementHandler('header-four'),
  392. },
  393. 'to_database_format': {
  394. 'block_map': {'header-four': 'h4'}
  395. }
  396. })
  397. features.register_editor_plugin(
  398. 'draftail', 'h5', draftail_features.BlockFeature({
  399. 'label': 'H5',
  400. 'type': 'header-five',
  401. 'description': gettext('Heading %(level)d') % {'level': 5},
  402. })
  403. )
  404. features.register_converter_rule('contentstate', 'h5', {
  405. 'from_database_format': {
  406. 'h5': BlockElementHandler('header-five'),
  407. },
  408. 'to_database_format': {
  409. 'block_map': {'header-five': 'h5'}
  410. }
  411. })
  412. features.register_editor_plugin(
  413. 'draftail', 'h6', draftail_features.BlockFeature({
  414. 'label': 'H6',
  415. 'type': 'header-six',
  416. 'description': gettext('Heading %(level)d') % {'level': 6},
  417. })
  418. )
  419. features.register_converter_rule('contentstate', 'h6', {
  420. 'from_database_format': {
  421. 'h6': BlockElementHandler('header-six'),
  422. },
  423. 'to_database_format': {
  424. 'block_map': {'header-six': 'h6'}
  425. }
  426. })
  427. features.register_editor_plugin(
  428. 'draftail', 'ul', draftail_features.BlockFeature({
  429. 'type': 'unordered-list-item',
  430. 'icon': 'list-ul',
  431. 'description': gettext('Bulleted list'),
  432. })
  433. )
  434. features.register_converter_rule('contentstate', 'ul', {
  435. 'from_database_format': {
  436. 'ul': ListElementHandler('unordered-list-item'),
  437. 'li': ListItemElementHandler(),
  438. },
  439. 'to_database_format': {
  440. 'block_map': {'unordered-list-item': {'element': 'li', 'wrapper': 'ul'}}
  441. }
  442. })
  443. features.register_editor_plugin(
  444. 'draftail', 'ol', draftail_features.BlockFeature({
  445. 'type': 'ordered-list-item',
  446. 'icon': 'list-ol',
  447. 'description': gettext('Numbered list'),
  448. })
  449. )
  450. features.register_converter_rule('contentstate', 'ol', {
  451. 'from_database_format': {
  452. 'ol': ListElementHandler('ordered-list-item'),
  453. 'li': ListItemElementHandler(),
  454. },
  455. 'to_database_format': {
  456. 'block_map': {'ordered-list-item': {'element': 'li', 'wrapper': 'ol'}}
  457. }
  458. })
  459. features.register_editor_plugin(
  460. 'draftail', 'blockquote', draftail_features.BlockFeature({
  461. 'type': 'blockquote',
  462. 'icon': 'openquote',
  463. 'description': gettext('Blockquote'),
  464. })
  465. )
  466. features.register_converter_rule('contentstate', 'blockquote', {
  467. 'from_database_format': {
  468. 'blockquote': BlockElementHandler('blockquote'),
  469. },
  470. 'to_database_format': {
  471. 'block_map': {'blockquote': 'blockquote'}
  472. }
  473. })
  474. features.register_editor_plugin(
  475. 'draftail', 'bold', draftail_features.InlineStyleFeature({
  476. 'type': 'BOLD',
  477. 'icon': 'bold',
  478. 'description': gettext('Bold'),
  479. })
  480. )
  481. features.register_converter_rule('contentstate', 'bold', {
  482. 'from_database_format': {
  483. 'b': InlineStyleElementHandler('BOLD'),
  484. 'strong': InlineStyleElementHandler('BOLD'),
  485. },
  486. 'to_database_format': {
  487. 'style_map': {'BOLD': 'b'}
  488. }
  489. })
  490. features.register_editor_plugin(
  491. 'draftail', 'italic', draftail_features.InlineStyleFeature({
  492. 'type': 'ITALIC',
  493. 'icon': 'italic',
  494. 'description': gettext('Italic'),
  495. })
  496. )
  497. features.register_converter_rule('contentstate', 'italic', {
  498. 'from_database_format': {
  499. 'i': InlineStyleElementHandler('ITALIC'),
  500. 'em': InlineStyleElementHandler('ITALIC'),
  501. },
  502. 'to_database_format': {
  503. 'style_map': {'ITALIC': 'i'}
  504. }
  505. })
  506. features.register_editor_plugin(
  507. 'draftail', 'link', draftail_features.EntityFeature({
  508. 'type': 'LINK',
  509. 'icon': 'link',
  510. 'description': gettext('Link'),
  511. # We want to enforce constraints on which links can be pasted into rich text.
  512. # Keep only the attributes Wagtail needs.
  513. 'attributes': ['url', 'id', 'parentId'],
  514. 'whitelist': {
  515. # Keep pasted links with http/https protocol, and not-pasted links (href = undefined).
  516. 'href': "^(http:|https:|undefined$)",
  517. }
  518. }, js=[
  519. 'wagtailadmin/js/page-chooser-modal.js',
  520. ])
  521. )
  522. features.register_converter_rule('contentstate', 'link', {
  523. 'from_database_format': {
  524. 'a[href]': ExternalLinkElementHandler('LINK'),
  525. 'a[linktype="page"]': PageLinkElementHandler('LINK'),
  526. },
  527. 'to_database_format': {
  528. 'entity_decorators': {'LINK': link_entity}
  529. }
  530. })
  531. features.register_editor_plugin(
  532. 'draftail', 'superscript', draftail_features.InlineStyleFeature({
  533. 'type': 'SUPERSCRIPT',
  534. 'icon': 'superscript',
  535. 'description': gettext('Superscript'),
  536. })
  537. )
  538. features.register_converter_rule('contentstate', 'superscript', {
  539. 'from_database_format': {
  540. 'sup': InlineStyleElementHandler('SUPERSCRIPT'),
  541. },
  542. 'to_database_format': {
  543. 'style_map': {'SUPERSCRIPT': 'sup'}
  544. }
  545. })
  546. features.register_editor_plugin(
  547. 'draftail', 'subscript', draftail_features.InlineStyleFeature({
  548. 'type': 'SUBSCRIPT',
  549. 'icon': 'subscript',
  550. 'description': gettext('Subscript'),
  551. })
  552. )
  553. features.register_converter_rule('contentstate', 'subscript', {
  554. 'from_database_format': {
  555. 'sub': InlineStyleElementHandler('SUBSCRIPT'),
  556. },
  557. 'to_database_format': {
  558. 'style_map': {'SUBSCRIPT': 'sub'}
  559. }
  560. })
  561. features.register_editor_plugin(
  562. 'draftail', 'strikethrough', draftail_features.InlineStyleFeature({
  563. 'type': 'STRIKETHROUGH',
  564. 'icon': 'strikethrough',
  565. 'description': gettext('Strikethrough'),
  566. })
  567. )
  568. features.register_converter_rule('contentstate', 'strikethrough', {
  569. 'from_database_format': {
  570. 's': InlineStyleElementHandler('STRIKETHROUGH'),
  571. },
  572. 'to_database_format': {
  573. 'style_map': {'STRIKETHROUGH': 's'}
  574. }
  575. })
  576. features.register_editor_plugin(
  577. 'draftail', 'code', draftail_features.InlineStyleFeature({
  578. 'type': 'CODE',
  579. 'icon': 'code',
  580. 'description': gettext('Code'),
  581. })
  582. )
  583. features.register_converter_rule('contentstate', 'code', {
  584. 'from_database_format': {
  585. 'code': InlineStyleElementHandler('CODE'),
  586. },
  587. 'to_database_format': {
  588. 'style_map': {'CODE': 'code'}
  589. }
  590. })
  591. class ReportsMenuItem(SubmenuMenuItem):
  592. template = 'wagtailadmin/shared/menu_submenu_item.html'
  593. class LockedPagesMenuItem(MenuItem):
  594. def is_shown(self, request):
  595. return UserPagePermissionsProxy(request.user).can_remove_locks()
  596. class WorkflowReportMenuItem(MenuItem):
  597. def is_shown(self, request):
  598. return True
  599. class SiteHistoryReportMenuItem(MenuItem):
  600. def is_shown(self, request):
  601. return UserPagePermissionsProxy(request.user).explorable_pages().exists()
  602. @hooks.register('register_reports_menu_item')
  603. def register_locked_pages_menu_item():
  604. return LockedPagesMenuItem(_('Locked Pages'), reverse('wagtailadmin_reports:locked_pages'), icon_name='lock', order=700)
  605. @hooks.register('register_reports_menu_item')
  606. def register_workflow_report_menu_item():
  607. return WorkflowReportMenuItem(_('Workflows'), reverse('wagtailadmin_reports:workflow'), icon_name='tasks', order=800)
  608. @hooks.register('register_reports_menu_item')
  609. def register_workflow_tasks_report_menu_item():
  610. return WorkflowReportMenuItem(_('Workflow tasks'), reverse('wagtailadmin_reports:workflow_tasks'), icon_name='thumbtack', order=900)
  611. @hooks.register('register_reports_menu_item')
  612. def register_site_history_report_menu_item():
  613. return SiteHistoryReportMenuItem(_('Site history'), reverse('wagtailadmin_reports:site_history'), icon_name='history', order=1000)
  614. @hooks.register('register_admin_menu_item')
  615. def register_reports_menu():
  616. return ReportsMenuItem(
  617. _('Reports'), reports_menu, icon_name='site', order=9000)
  618. @hooks.register('register_icons')
  619. def register_icons(icons):
  620. for icon in [
  621. 'arrow-down-big.svg',
  622. 'arrow-down.svg',
  623. 'arrow-left.svg',
  624. 'arrow-right.svg',
  625. 'arrow-up-big.svg',
  626. 'arrow-up.svg',
  627. 'arrows-up-down.svg',
  628. 'bin.svg',
  629. 'bold.svg',
  630. 'chain-broken.svg',
  631. 'clipboard-list.svg',
  632. 'code.svg',
  633. 'cog.svg',
  634. 'cogs.svg',
  635. 'collapse-down.svg',
  636. 'collapse-up.svg',
  637. 'comment.svg',
  638. 'cross.svg',
  639. 'date.svg',
  640. 'doc-empty-inverse.svg',
  641. 'doc-empty.svg',
  642. 'doc-full-inverse.svg',
  643. 'doc-full.svg', # aka file-text-alt
  644. 'download-alt.svg',
  645. 'download.svg',
  646. 'draft.svg',
  647. 'duplicate.svg',
  648. 'edit.svg',
  649. 'error.svg',
  650. 'folder-inverse.svg',
  651. 'folder-open-1.svg',
  652. 'folder-open-inverse.svg',
  653. 'folder.svg',
  654. 'form.svg',
  655. 'grip.svg',
  656. 'group.svg',
  657. 'help.svg',
  658. 'history.svg',
  659. 'home.svg',
  660. 'horizontalrule.svg',
  661. 'image.svg', # aka picture
  662. 'italic.svg',
  663. 'link.svg',
  664. 'link-external.svg',
  665. 'list-ol.svg',
  666. 'list-ul.svg',
  667. 'lock-open.svg',
  668. 'lock.svg',
  669. 'login.svg',
  670. 'logout.svg',
  671. 'mail.svg',
  672. 'media.svg',
  673. 'no-view.svg',
  674. 'openquote.svg',
  675. 'order-down.svg',
  676. 'order-up.svg',
  677. 'order.svg',
  678. 'password.svg',
  679. 'pick.svg',
  680. 'pilcrow.svg',
  681. 'placeholder.svg', # aka marquee
  682. 'plus-inverse.svg',
  683. 'plus.svg',
  684. 'radio-empty.svg',
  685. 'radio-full.svg',
  686. 'redirect.svg',
  687. 'repeat.svg',
  688. 'reset.svg',
  689. 'resubmit.svg',
  690. 'search.svg',
  691. 'site.svg',
  692. 'snippet.svg',
  693. 'spinner.svg',
  694. 'strikethrough.svg',
  695. 'success.svg',
  696. 'subscript.svg',
  697. 'superscript.svg',
  698. 'table.svg',
  699. 'tag.svg',
  700. 'tasks.svg',
  701. 'thumbtack.svg',
  702. 'tick-inverse.svg',
  703. 'tick.svg',
  704. 'time.svg',
  705. 'title.svg',
  706. 'undo.svg',
  707. 'uni52.svg', # Is this a redundant icon?
  708. 'upload.svg',
  709. 'user.svg',
  710. 'view.svg',
  711. 'wagtail-inverse.svg',
  712. 'wagtail.svg',
  713. 'warning.svg',
  714. ]:
  715. icons.append('wagtailadmin/icons/{}'.format(icon))
  716. return icons
  717. @hooks.register('construct_homepage_summary_items')
  718. def add_pages_summary_item(request, items):
  719. items.insert(0, PagesSummaryItem(request))