collective.bbcodesnippets 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- collective/bbcodesnippets/__init__.py +5 -0
- collective/bbcodesnippets/configure.zcml +106 -0
- collective/bbcodesnippets/controlpanel.py +49 -0
- collective/bbcodesnippets/demo.pt +17 -0
- collective/bbcodesnippets/demo.py +18 -0
- collective/bbcodesnippets/formatters.py +303 -0
- collective/bbcodesnippets/formatters.zcml +65 -0
- collective/bbcodesnippets/indexer.py +28 -0
- collective/bbcodesnippets/interfaces.py +20 -0
- collective/bbcodesnippets/menu.py +80 -0
- collective/bbcodesnippets/parser.py +49 -0
- collective/bbcodesnippets/profiles/default/browserlayer.xml +6 -0
- collective/bbcodesnippets/profiles/default/catalog.xml +12 -0
- collective/bbcodesnippets/profiles/default/controlpanel.xml +19 -0
- collective/bbcodesnippets/profiles/default/metadata.xml +7 -0
- collective/bbcodesnippets/profiles/default/registry.xml +26 -0
- collective/bbcodesnippets/profiles/uninstall/browserlayer.xml +6 -0
- collective/bbcodesnippets/profiles/uninstall/controlpanel.xml +11 -0
- collective/bbcodesnippets/profiles/uninstall/registry.xml +25 -0
- collective/bbcodesnippets/restapi.py +22 -0
- collective/bbcodesnippets/setuphandlers.py +21 -0
- collective/bbcodesnippets/static/bbcodeicon.png +0 -0
- collective/bbcodesnippets/static/bbcodeicon.svg +72 -0
- collective/bbcodesnippets/static/collective.bbcodesnippets.js +34 -0
- collective/bbcodesnippets/static/collective.bbcodesnippets.js.map +1 -0
- collective/bbcodesnippets/static/collective.bbcodesnippets.min.js +1 -0
- collective/bbcodesnippets/static/collective.bookmarks.css +2 -0
- collective/bbcodesnippets/static/collective.bookmarks.css.map +8 -0
- collective/bbcodesnippets/testing.py +34 -0
- collective/bbcodesnippets/tests/__init__.py +0 -0
- collective/bbcodesnippets/tests/mocks.py +5 -0
- collective/bbcodesnippets/tests/test_parser.py +57 -0
- collective/bbcodesnippets/tests/test_setup.py +58 -0
- collective/bbcodesnippets/tests/test_transform.py +130 -0
- collective/bbcodesnippets/tinymce.py +39 -0
- collective/bbcodesnippets/transform.py +145 -0
- collective/bbcodesnippets/upgrades/2.zcml +27 -0
- collective/bbcodesnippets/upgrades/__init__.py +0 -0
- collective/bbcodesnippets/upgrades/configure.zcml +5 -0
- collective/bbcodesnippets/upgrades/profiles/2/registry.xml +7 -0
- collective_bbcodesnippets-2.0.0.dist-info/METADATA +102 -0
- collective_bbcodesnippets-2.0.0.dist-info/RECORD +44 -0
- collective_bbcodesnippets-2.0.0.dist-info/WHEEL +4 -0
- collective_bbcodesnippets-2.0.0.dist-info/licenses/LICENSE.rst +29 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
<configure
|
|
2
|
+
xmlns="http://namespaces.zope.org/zope"
|
|
3
|
+
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
|
|
4
|
+
xmlns:browser="http://namespaces.zope.org/browser"
|
|
5
|
+
xmlns:i18n="http://namespaces.zope.org/i18n"
|
|
6
|
+
xmlns:plone="http://namespaces.plone.org/plone"
|
|
7
|
+
i18n_domain="collective.bbcodesnippets">
|
|
8
|
+
|
|
9
|
+
<!-- <i18n:registerTranslations directory="locales" /> -->
|
|
10
|
+
|
|
11
|
+
<include package="Products.CMFPlone" />
|
|
12
|
+
<include package="plone.restapi" />
|
|
13
|
+
|
|
14
|
+
<include file="formatters.zcml" />
|
|
15
|
+
|
|
16
|
+
<!-- Controlpanel -->
|
|
17
|
+
<genericsetup:registerProfile
|
|
18
|
+
name="default"
|
|
19
|
+
title="BBCode Snippets"
|
|
20
|
+
directory="profiles/default"
|
|
21
|
+
description="Installs the collective.bbcodesnippets add-on."
|
|
22
|
+
provides="Products.GenericSetup.interfaces.EXTENSION"
|
|
23
|
+
post_handler=".setuphandlers.post_install"
|
|
24
|
+
/>
|
|
25
|
+
|
|
26
|
+
<genericsetup:registerProfile
|
|
27
|
+
name="uninstall"
|
|
28
|
+
title="BBCode Snippets (uninstall)"
|
|
29
|
+
directory="profiles/uninstall"
|
|
30
|
+
description="Uninstalls the collective.bbcodesnippets add-on."
|
|
31
|
+
provides="Products.GenericSetup.interfaces.EXTENSION"
|
|
32
|
+
post_handler=".setuphandlers.uninstall"
|
|
33
|
+
/>
|
|
34
|
+
|
|
35
|
+
<!-- Has to go after the default profile definition -->
|
|
36
|
+
<include package=".upgrades" />
|
|
37
|
+
|
|
38
|
+
<utility
|
|
39
|
+
factory=".setuphandlers.HiddenProfiles"
|
|
40
|
+
name="collective.bbcodesnippets-hiddenprofiles"
|
|
41
|
+
/>
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
<!-- Controlpanel -->
|
|
45
|
+
<browser:page
|
|
46
|
+
name="bbcodesnippets-controlpanel"
|
|
47
|
+
for="plone.base.interfaces.IPloneSiteRoot"
|
|
48
|
+
permission="plone.app.controlpanel.Markup"
|
|
49
|
+
class=".controlpanel.BBCodeControlPanel"
|
|
50
|
+
layer=".interfaces.IBBCodeSnippetsLayer"
|
|
51
|
+
/>
|
|
52
|
+
<utility
|
|
53
|
+
component=".controlpanel.available_formatters_vocabulary_factory"
|
|
54
|
+
name="bbcodesnippets.available_formatters_vocabulary"
|
|
55
|
+
/>
|
|
56
|
+
|
|
57
|
+
<!-- transformer -->
|
|
58
|
+
<adapter
|
|
59
|
+
name="collective.bbcodesnippets.transform"
|
|
60
|
+
factory=".transform.BBCodeSnippetsTransform"
|
|
61
|
+
/>
|
|
62
|
+
|
|
63
|
+
<!-- Toolbar Menu -->
|
|
64
|
+
<adapter for="* .interfaces.IBBCodeSnippetsLayer"
|
|
65
|
+
name="plone.contentmenu.bbcodesnippets_menu_item"
|
|
66
|
+
factory=".menu.BBCodeSnippetesMainMenuItem"
|
|
67
|
+
provides="plone.app.contentmenu.interfaces.IContentMenuItem" />
|
|
68
|
+
|
|
69
|
+
<browser:menu
|
|
70
|
+
id="bbcodesnippets_menu"
|
|
71
|
+
title="BBCode Snippets"
|
|
72
|
+
class=".menu.BBCodeSnippetesMenu"
|
|
73
|
+
/>
|
|
74
|
+
|
|
75
|
+
<!-- Restapi -->
|
|
76
|
+
<plone:service
|
|
77
|
+
method="GET"
|
|
78
|
+
for="zope.interface.Interface"
|
|
79
|
+
layer=".interfaces.IBBCodeSnippetsLayer"
|
|
80
|
+
factory=".restapi.EnabledSnippetsGet"
|
|
81
|
+
name="@bbcodesnippets_enabled"
|
|
82
|
+
permission="zope2.View"
|
|
83
|
+
/>
|
|
84
|
+
|
|
85
|
+
<!-- Publish static files -->
|
|
86
|
+
<plone:static
|
|
87
|
+
name="collective.bbcodesnippets"
|
|
88
|
+
type="plone"
|
|
89
|
+
directory="static"
|
|
90
|
+
/>
|
|
91
|
+
|
|
92
|
+
<!-- indexer -->
|
|
93
|
+
<adapter
|
|
94
|
+
factory=".indexer.has_bbcode"
|
|
95
|
+
name="has_bbcode"
|
|
96
|
+
/>
|
|
97
|
+
|
|
98
|
+
<!-- Demo -->
|
|
99
|
+
<browser:page
|
|
100
|
+
name="bbcodesnippets-demo"
|
|
101
|
+
for="plone.base.interfaces.IPloneSiteRoot"
|
|
102
|
+
permission="plone.app.controlpanel.Markup"
|
|
103
|
+
class=".demo.DemoView"
|
|
104
|
+
template="demo.pt"
|
|
105
|
+
/>
|
|
106
|
+
</configure>
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from . import _
|
|
2
|
+
from .interfaces import IFormatterFactory
|
|
3
|
+
from operator import itemgetter
|
|
4
|
+
from plone.app.registry.browser import controlpanel
|
|
5
|
+
from plone.app.vocabularies.terms import TermWithDescription
|
|
6
|
+
from z3c.form.browser.checkbox import CheckBoxFieldWidget
|
|
7
|
+
from zope import schema
|
|
8
|
+
from zope.component import getUtilitiesFor
|
|
9
|
+
from zope.interface import Interface
|
|
10
|
+
from zope.interface import provider
|
|
11
|
+
from zope.schema.interfaces import IVocabularyFactory
|
|
12
|
+
from zope.schema.vocabulary import SimpleVocabulary
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@provider(IVocabularyFactory)
|
|
16
|
+
def available_formatters_vocabulary_factory(context):
|
|
17
|
+
terms = []
|
|
18
|
+
for name, factory in sorted(getUtilitiesFor(IFormatterFactory), key=itemgetter(0)):
|
|
19
|
+
terms.append(TermWithDescription(name, name, name, description=factory.__doc__))
|
|
20
|
+
return SimpleVocabulary(terms)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class IBBCodeSnippetsSettings(Interface):
|
|
24
|
+
formatters = schema.List(
|
|
25
|
+
title=_("Available Formatters"),
|
|
26
|
+
description=_("Selected formatters will be enabled in the portal."),
|
|
27
|
+
value_type=schema.Choice(
|
|
28
|
+
vocabulary="bbcodesnippets.available_formatters_vocabulary",
|
|
29
|
+
),
|
|
30
|
+
default=[],
|
|
31
|
+
missing_value=[],
|
|
32
|
+
required=False,
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class BBCodeControlPanelForm(controlpanel.RegistryEditForm):
|
|
37
|
+
id = "BBCodeControlPanel"
|
|
38
|
+
label = _("BBCode Snippets")
|
|
39
|
+
description = _("BBCode snippets settings.")
|
|
40
|
+
schema = IBBCodeSnippetsSettings
|
|
41
|
+
schema_prefix = "bbcodesnippets"
|
|
42
|
+
|
|
43
|
+
def updateFields(self):
|
|
44
|
+
super().updateFields()
|
|
45
|
+
self.fields["formatters"].widgetFactory = CheckBoxFieldWidget
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class BBCodeControlPanel(controlpanel.ControlPanelFormWrapper):
|
|
49
|
+
form = BBCodeControlPanelForm
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" xmlns:tal="http://xml.zope.org/namespaces/tal" xmlns:metal="http://xml.zope.org/namespaces/metal" xmlns:i18n="http://xml.zope.org/namespaces/i18n" lang="en" metal:use-macro="context/@@main_template/macros/master" i18n:domain="kup.akivdb">
|
|
2
|
+
|
|
3
|
+
<body>
|
|
4
|
+
<metal:content-core fill-slot="content-title">
|
|
5
|
+
<h1>BBCode Snippets Demo</h1>
|
|
6
|
+
</metal:content-core>
|
|
7
|
+
<metal:content-core fill-slot="content-core">
|
|
8
|
+
<metal:block define-macro="content-core">
|
|
9
|
+
<article tal:repeat="docssnippet python:view.docsnippets()">
|
|
10
|
+
<h2 tal:define="snippet python:docssnippet['snippet']">${python:docssnippet['name']} <a class="small" title="Copy ${snippet} to clipboard" href="javascript:navigator.clipboard.writeText('${snippet}');">Copy</a></h2>
|
|
11
|
+
<p class="small">[${python:'X' if docssnippet['enabled'] else '-'}] enabled</p>
|
|
12
|
+
<tal tal:replace="structure python:docssnippet['demo']" >Demo</tal>
|
|
13
|
+
</article>
|
|
14
|
+
</metal:block>
|
|
15
|
+
</metal:content-core>
|
|
16
|
+
</body>
|
|
17
|
+
</html>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from .interfaces import IFormatterFactory
|
|
2
|
+
from plone import api
|
|
3
|
+
from Products.Five.browser import BrowserView
|
|
4
|
+
from zope.component import getUtilitiesFor
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class DemoView(BrowserView):
|
|
8
|
+
def docsnippets(self):
|
|
9
|
+
enabled = api.portal.get_registry_record("bbcodesnippets.formatters")
|
|
10
|
+
for name, factory in getUtilitiesFor(IFormatterFactory):
|
|
11
|
+
if not factory.__doc__:
|
|
12
|
+
continue
|
|
13
|
+
yield {
|
|
14
|
+
"name": name,
|
|
15
|
+
"snippet": factory.__bbcode_copy_snippet__,
|
|
16
|
+
"demo": factory.__doc__,
|
|
17
|
+
"enabled": name in enabled,
|
|
18
|
+
}
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
from .interfaces import IFormatterFactory
|
|
2
|
+
from zope.interface import provider
|
|
3
|
+
|
|
4
|
+
import bbcode
|
|
5
|
+
import re
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class copy_snippet:
|
|
9
|
+
def __init__(self, snippet):
|
|
10
|
+
self.__bbcode_copy_snippet__ = snippet
|
|
11
|
+
|
|
12
|
+
def __call__(self, func):
|
|
13
|
+
func.__bbcode_copy_snippet__ = self.__bbcode_copy_snippet__
|
|
14
|
+
return func
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class template_snippet:
|
|
18
|
+
def __init__(self, snippet):
|
|
19
|
+
self.__bbcode_template_snippet__ = snippet
|
|
20
|
+
|
|
21
|
+
def __call__(self, func):
|
|
22
|
+
func.__bbcode_template_snippet__ = self.__bbcode_template_snippet__
|
|
23
|
+
return func
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# parts of this code are inspired by and partly taken from
|
|
27
|
+
# https://github.com/dcwatson/bbcode/blob/master/bbcode.py
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# b [b]test[/b] <strong>test</strong>
|
|
31
|
+
# i [i]test[/i] <em>test</em>
|
|
32
|
+
# u [u]test[/u] <u>test</u>
|
|
33
|
+
# s [s]test[/s] <strike>test</strike>
|
|
34
|
+
# hr [hr] <hr />
|
|
35
|
+
# br [br] <br />
|
|
36
|
+
# sub x[sub]3[/sub] x<sub>3</sub>
|
|
37
|
+
# sup x[sup]3[/sup] x<sup>3</sup>
|
|
38
|
+
# list/* [list][*] item[/list] <ul><li>item</li></ul>
|
|
39
|
+
# quote [quote]hello[/quote] <blockquote>hello</blockquote>
|
|
40
|
+
# code [code]x = 3[/code] <code>x = 3</code>
|
|
41
|
+
# center [center]hello[/center] <div style="text-align:center;">hello</div>
|
|
42
|
+
# color [color=red]red[/color] <span style="color:red;">red</span>
|
|
43
|
+
# url [url=www.apple.com]Apple[/url] <a href="http://www.apple.com">Apple</a>
|
|
44
|
+
|
|
45
|
+
TEMPLATES = {
|
|
46
|
+
"url": '<a rel="nofollow" href="{href}">{text}</a>',
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def make_simple_formatter(tag_name, format_string, **kwargs):
|
|
51
|
+
"""
|
|
52
|
+
Creates a formatter that takes the tag options dictionary, puts a value key
|
|
53
|
+
in it, and uses it as a format dictionary to the given format string.
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def _render(name, value, options, parent, context):
|
|
57
|
+
fmt = {}
|
|
58
|
+
if options:
|
|
59
|
+
fmt.update(options)
|
|
60
|
+
fmt.update({"value": value})
|
|
61
|
+
return format_string % fmt
|
|
62
|
+
|
|
63
|
+
return _render
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@provider(IFormatterFactory)
|
|
67
|
+
@copy_snippet("[b][/b]")
|
|
68
|
+
@template_snippet("[b]$TEXT[/b]$CURSOR")
|
|
69
|
+
def b_factory():
|
|
70
|
+
"""Bold or strong text: <pre>A [b]bold[/b] text.</pre><br />
|
|
71
|
+
Example:<br />
|
|
72
|
+
A [b]bold[/b] text.
|
|
73
|
+
"""
|
|
74
|
+
return make_simple_formatter("b", "<strong>%(value)s</strong>"), {}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@provider(IFormatterFactory)
|
|
78
|
+
@copy_snippet("[i][/i]")
|
|
79
|
+
@template_snippet("[i]$TEXT[/i]$CURSOR")
|
|
80
|
+
def i_factory():
|
|
81
|
+
"""Italic or emphasised text: <pre>An [i]italic/emphasised[/i] text.</pre><br />
|
|
82
|
+
Example:<br />
|
|
83
|
+
An [i]italic/emphasised[/i] text.
|
|
84
|
+
"""
|
|
85
|
+
return make_simple_formatter("i", "<em>%(value)s</em>"), {}
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@provider(IFormatterFactory)
|
|
89
|
+
@copy_snippet("[u][/u]")
|
|
90
|
+
@template_snippet("[u]$TEXT[/u]$CURSOR")
|
|
91
|
+
def u_factory():
|
|
92
|
+
"""Underlined or unarticulated text: <pre>An [u]underlined[u] text.</pre><br />
|
|
93
|
+
Example:<br />
|
|
94
|
+
An [u]underlined[/u] text.
|
|
95
|
+
"""
|
|
96
|
+
return make_simple_formatter("u", "<u>%(value)s</u>"), {}
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@provider(IFormatterFactory)
|
|
100
|
+
@copy_snippet("[s][/s]")
|
|
101
|
+
@template_snippet("[s]$TEXT[/s]$CURSOR")
|
|
102
|
+
def s_factory():
|
|
103
|
+
"""Strike through or deleted text: [s]test[/s]"""
|
|
104
|
+
return make_simple_formatter("s", "<del>%(value)s</del>"), {}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
@provider(IFormatterFactory)
|
|
108
|
+
@copy_snippet("[sub][/sub]")
|
|
109
|
+
@template_snippet("[sub]$TEXT[/sub]$CURSOR")
|
|
110
|
+
def sub_factory():
|
|
111
|
+
"""Subscript text: <pre>H[sub]2[/sub]O</pre><br />
|
|
112
|
+
Example:<br />
|
|
113
|
+
H[sub]2[/sub]O
|
|
114
|
+
"""
|
|
115
|
+
return make_simple_formatter("sub", "<sub>%(value)s</sub>"), {}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
@provider(IFormatterFactory)
|
|
119
|
+
@copy_snippet("[sup][/sup]")
|
|
120
|
+
@template_snippet("[sup]$TEXT[/sup]$CURSOR")
|
|
121
|
+
def sup_factory():
|
|
122
|
+
"""Superscript text: <pre>r[sup]2[/sup]</pre><br />
|
|
123
|
+
Example:<br />
|
|
124
|
+
r[sup]2[/sup]
|
|
125
|
+
"""
|
|
126
|
+
return make_simple_formatter("sup", "<sup>%(value)s</sup>"), {}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
@provider(IFormatterFactory)
|
|
130
|
+
@copy_snippet("[hr]")
|
|
131
|
+
@template_snippet("$TEXT[hr]$CURSOR")
|
|
132
|
+
def hr_factory():
|
|
133
|
+
"""Horizontal ruler: <pre>Above ruler[hr]Below ruler</pre><br />
|
|
134
|
+
Example:<br />
|
|
135
|
+
|
|
136
|
+
Above ruler[hr]Below ruler
|
|
137
|
+
"""
|
|
138
|
+
return make_simple_formatter("hr", "<hr />"), {"standalone": True}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
@provider(IFormatterFactory)
|
|
142
|
+
@copy_snippet("[br]")
|
|
143
|
+
@template_snippet("$TEXT[br]$CURSOR")
|
|
144
|
+
def br_factory():
|
|
145
|
+
"""Line break in text: <pre>A line[br]break in the text.</pre><br />
|
|
146
|
+
Example:<br />
|
|
147
|
+
A line[br]break in the text.
|
|
148
|
+
"""
|
|
149
|
+
return make_simple_formatter("br", "<br />"), {"standalone": True}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
@provider(IFormatterFactory)
|
|
153
|
+
@copy_snippet("""\
|
|
154
|
+
[list]
|
|
155
|
+
[*] item
|
|
156
|
+
[/list]
|
|
157
|
+
""")
|
|
158
|
+
@template_snippet("""\
|
|
159
|
+
[list]
|
|
160
|
+
[*] $TEXT
|
|
161
|
+
[*] $CURSOR
|
|
162
|
+
[/list]
|
|
163
|
+
""")
|
|
164
|
+
def list_factory():
|
|
165
|
+
"""List with bullets or numbers. Use '*' for bullet points or for numbers one out of '1', '01, 'a', 'A', 'i' or 'I'.
|
|
166
|
+
Bullet points: [list][*] item[/list]
|
|
167
|
+
Numbered: [list][1] item[/list]
|
|
168
|
+
"""
|
|
169
|
+
|
|
170
|
+
def _render_list(name, value, options, parent, context):
|
|
171
|
+
list_type = options["list"] if (options and "list" in options) else "*"
|
|
172
|
+
css_opts = {
|
|
173
|
+
"1": "decimal",
|
|
174
|
+
"01": "decimal-leading-zero",
|
|
175
|
+
"a": "lower-alpha",
|
|
176
|
+
"A": "upper-alpha",
|
|
177
|
+
"i": "lower-roman",
|
|
178
|
+
"I": "upper-roman",
|
|
179
|
+
}
|
|
180
|
+
tag = "ol" if list_type in css_opts else "ul"
|
|
181
|
+
css = (
|
|
182
|
+
f' style="list-style-type: {css_opts[list_type]};"'
|
|
183
|
+
if list_type in css_opts
|
|
184
|
+
else ""
|
|
185
|
+
)
|
|
186
|
+
return f"<{tag}{css}>{value}</{tag}>"
|
|
187
|
+
|
|
188
|
+
return _render_list, {
|
|
189
|
+
"transform_newlines": False,
|
|
190
|
+
"strip": True,
|
|
191
|
+
"swallow_trailing_newline": True,
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
@provider(IFormatterFactory)
|
|
196
|
+
def list_item_factory():
|
|
197
|
+
# no doc string, so will not appear in documentation, helper for list
|
|
198
|
+
def _render_list_item(name, value, options, parent, context):
|
|
199
|
+
if not parent or parent.tag_name != "list":
|
|
200
|
+
return f"[*]{value}<br />"
|
|
201
|
+
|
|
202
|
+
return f"<li>{value}</li>"
|
|
203
|
+
|
|
204
|
+
return _render_list_item, {
|
|
205
|
+
"newline_closes": True,
|
|
206
|
+
"transform_newlines": False,
|
|
207
|
+
"same_tag_closes": True,
|
|
208
|
+
"strip": True,
|
|
209
|
+
"level": 1,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
|
|
213
|
+
@provider(IFormatterFactory)
|
|
214
|
+
@copy_snippet("[quote][/quote]")
|
|
215
|
+
@template_snippet("[quote]$TEXT[/quote]$CURSOR")
|
|
216
|
+
def quote_factory():
|
|
217
|
+
return make_simple_formatter("quote", "<blockquote>%(value)s</blockquote>"), {
|
|
218
|
+
"strip": True,
|
|
219
|
+
"swallow_trailing_newline": True,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
@provider(IFormatterFactory)
|
|
224
|
+
@copy_snippet("[code][/code]")
|
|
225
|
+
@template_snippet("[code]$TEXT[/code]$CURSOR")
|
|
226
|
+
def code_factory():
|
|
227
|
+
"""Code text: <pre>Some random Code: [code]print("Hello World")[/code]</pre><br />
|
|
228
|
+
Example:<br />
|
|
229
|
+
Some random Code: [code]print("Hello World")[/code]
|
|
230
|
+
"""
|
|
231
|
+
|
|
232
|
+
return make_simple_formatter("code", "<code>%(value)s</code>"), {
|
|
233
|
+
"render_embedded": False,
|
|
234
|
+
"transform_newlines": False,
|
|
235
|
+
"swallow_trailing_newline": True,
|
|
236
|
+
"replace_cosmetic": False,
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
@provider(IFormatterFactory)
|
|
241
|
+
@copy_snippet("[color=red][/color]")
|
|
242
|
+
@template_snippet("[color=$CURSOR]$TEXT[/color]")
|
|
243
|
+
def color_factory():
|
|
244
|
+
def _render_color(name, value, options, parent, context):
|
|
245
|
+
if "color" in options:
|
|
246
|
+
color = options["color"].strip()
|
|
247
|
+
elif options:
|
|
248
|
+
color = next(iter(options.keys())).strip()
|
|
249
|
+
else:
|
|
250
|
+
return value
|
|
251
|
+
match = re.match(r"^([a-z]+)|^(#[a-f0-9]{3,6})", color, re.I)
|
|
252
|
+
color = match.group() if match else "inherit"
|
|
253
|
+
return f'<span style="color:{color};">{value}</span>'
|
|
254
|
+
|
|
255
|
+
return _render_color(), {}
|
|
256
|
+
|
|
257
|
+
|
|
258
|
+
@provider(IFormatterFactory)
|
|
259
|
+
@copy_snippet("[center][/center]")
|
|
260
|
+
@template_snippet("[center]$TEXT[/center]$CURSOR")
|
|
261
|
+
def center_factory():
|
|
262
|
+
"""Centered text: <pre>[center]centered text[/center]</pre><br />
|
|
263
|
+
Example:<br />
|
|
264
|
+
[center]centered text[/center]
|
|
265
|
+
"""
|
|
266
|
+
return (
|
|
267
|
+
make_simple_formatter(
|
|
268
|
+
"center", '<div style="text-align:center;">%(value)s</div>'
|
|
269
|
+
),
|
|
270
|
+
{},
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
@provider(IFormatterFactory)
|
|
275
|
+
@copy_snippet("[url=https://plone.org]Plone[/url]")
|
|
276
|
+
@template_snippet("[url=$CURSOR]$TEXT[/url]")
|
|
277
|
+
def url_factory():
|
|
278
|
+
"""A hyper link in the text: <pre>Welcome to [url=www.plone.org]Plone[/url]!</pre><br />
|
|
279
|
+
Example:<br />
|
|
280
|
+
Welcome to [url=www.plone.org]Plone[/url]!
|
|
281
|
+
"""
|
|
282
|
+
|
|
283
|
+
def _render_url(name, value, options, parent, context):
|
|
284
|
+
if options and "url" in options:
|
|
285
|
+
href = options["url"]
|
|
286
|
+
# Option values are not escaped for HTML output.
|
|
287
|
+
for find, repl in bbcode.Parser.REPLACE_ESCAPE:
|
|
288
|
+
value = value.replace(find, repl)
|
|
289
|
+
else:
|
|
290
|
+
href = value
|
|
291
|
+
# Completely ignore javascript: and data: "links".
|
|
292
|
+
if re.sub(r"[^a-z0-9+]", "", href.lower().split(":", 1)[0]) in (
|
|
293
|
+
"javascript",
|
|
294
|
+
"data",
|
|
295
|
+
"vbscript",
|
|
296
|
+
):
|
|
297
|
+
return ""
|
|
298
|
+
# Only add the missing https:// if it looks like it starts with a domain name.
|
|
299
|
+
if "://" not in href and bbcode._domain_re.match(href):
|
|
300
|
+
href = "https://" + href
|
|
301
|
+
return TEMPLATES["url"].format(href=href.replace('"', "%22"), text=value)
|
|
302
|
+
|
|
303
|
+
return _render_url, {"replace_links": False, "replace_cosmetic": False}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
<configure
|
|
2
|
+
xmlns="http://namespaces.zope.org/zope">
|
|
3
|
+
|
|
4
|
+
<utility
|
|
5
|
+
name="b"
|
|
6
|
+
component=".formatters.b_factory"
|
|
7
|
+
/>
|
|
8
|
+
<utility
|
|
9
|
+
name="i"
|
|
10
|
+
component=".formatters.i_factory"
|
|
11
|
+
/>
|
|
12
|
+
<utility
|
|
13
|
+
name="u"
|
|
14
|
+
component=".formatters.u_factory"
|
|
15
|
+
/>
|
|
16
|
+
<utility
|
|
17
|
+
name="s"
|
|
18
|
+
component=".formatters.s_factory"
|
|
19
|
+
/>
|
|
20
|
+
<utility
|
|
21
|
+
name="sub"
|
|
22
|
+
component=".formatters.sub_factory"
|
|
23
|
+
/>
|
|
24
|
+
<utility
|
|
25
|
+
name="sup"
|
|
26
|
+
component=".formatters.sup_factory"
|
|
27
|
+
/>
|
|
28
|
+
<utility
|
|
29
|
+
name="hr"
|
|
30
|
+
component=".formatters.hr_factory"
|
|
31
|
+
/>
|
|
32
|
+
<utility
|
|
33
|
+
name="br"
|
|
34
|
+
component=".formatters.br_factory"
|
|
35
|
+
/>
|
|
36
|
+
<utility
|
|
37
|
+
name="list"
|
|
38
|
+
component=".formatters.list_factory"
|
|
39
|
+
/>
|
|
40
|
+
<utility
|
|
41
|
+
name="list_item"
|
|
42
|
+
component=".formatters.list_item_factory"
|
|
43
|
+
/>
|
|
44
|
+
<utility
|
|
45
|
+
name="quote"
|
|
46
|
+
component=".formatters.quote_factory"
|
|
47
|
+
/>
|
|
48
|
+
<utility
|
|
49
|
+
name="code"
|
|
50
|
+
component=".formatters.code_factory"
|
|
51
|
+
/>
|
|
52
|
+
<!-- <utility
|
|
53
|
+
name="color"
|
|
54
|
+
component=".formatters.color_factory"
|
|
55
|
+
/> -->
|
|
56
|
+
<utility
|
|
57
|
+
name="center"
|
|
58
|
+
component=".formatters.center_factory"
|
|
59
|
+
/>
|
|
60
|
+
<utility
|
|
61
|
+
name="url"
|
|
62
|
+
component=".formatters.url_factory"
|
|
63
|
+
/>
|
|
64
|
+
|
|
65
|
+
</configure>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from plone.app.textfield.interfaces import IRichText
|
|
2
|
+
from plone.app.textfield.interfaces import IRichTextValue
|
|
3
|
+
from plone.base.utils import safe_text
|
|
4
|
+
from plone.dexterity.content import iterSchemata
|
|
5
|
+
from plone.indexer.decorator import indexer
|
|
6
|
+
from zope.interface import Interface
|
|
7
|
+
|
|
8
|
+
import re
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
_DETECTOR_RE = re.compile(r".*\[[a-z].*?\]")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@indexer(Interface)
|
|
15
|
+
def has_bbcode(obj):
|
|
16
|
+
for schema in iterSchemata(obj):
|
|
17
|
+
for field_name in schema:
|
|
18
|
+
field = schema.get(field_name, None)
|
|
19
|
+
value = getattr(obj, field_name, None)
|
|
20
|
+
if isinstance(value, bytes):
|
|
21
|
+
value = safe_text(value)
|
|
22
|
+
elif IRichText.providedBy(field):
|
|
23
|
+
if not IRichTextValue.providedBy(value):
|
|
24
|
+
continue
|
|
25
|
+
value = value.raw
|
|
26
|
+
if isinstance(value, str) and _DETECTOR_RE.search(value):
|
|
27
|
+
return True
|
|
28
|
+
return False
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
from zope.browsermenu.interfaces import IBrowserMenu
|
|
2
|
+
from zope.interface import Interface
|
|
3
|
+
from zope.publisher.interfaces.browser import IDefaultBrowserLayer
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class IBBCodeSnippetsLayer(IDefaultBrowserLayer):
|
|
7
|
+
"""Marker interface that defines a browser layer."""
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class IFormatterFactory(Interface):
|
|
11
|
+
def __call__():
|
|
12
|
+
"""create a new bbcode formatter."""
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class IBBCodeSnippetesMainMenuItem(IBrowserMenu):
|
|
16
|
+
"""The main BBCode menu item."""
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class IBBCodeSnippetesMenu(IBrowserMenu):
|
|
20
|
+
"""The BBCode menu."""
|