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.
Files changed (44) hide show
  1. collective/bbcodesnippets/__init__.py +5 -0
  2. collective/bbcodesnippets/configure.zcml +106 -0
  3. collective/bbcodesnippets/controlpanel.py +49 -0
  4. collective/bbcodesnippets/demo.pt +17 -0
  5. collective/bbcodesnippets/demo.py +18 -0
  6. collective/bbcodesnippets/formatters.py +303 -0
  7. collective/bbcodesnippets/formatters.zcml +65 -0
  8. collective/bbcodesnippets/indexer.py +28 -0
  9. collective/bbcodesnippets/interfaces.py +20 -0
  10. collective/bbcodesnippets/menu.py +80 -0
  11. collective/bbcodesnippets/parser.py +49 -0
  12. collective/bbcodesnippets/profiles/default/browserlayer.xml +6 -0
  13. collective/bbcodesnippets/profiles/default/catalog.xml +12 -0
  14. collective/bbcodesnippets/profiles/default/controlpanel.xml +19 -0
  15. collective/bbcodesnippets/profiles/default/metadata.xml +7 -0
  16. collective/bbcodesnippets/profiles/default/registry.xml +26 -0
  17. collective/bbcodesnippets/profiles/uninstall/browserlayer.xml +6 -0
  18. collective/bbcodesnippets/profiles/uninstall/controlpanel.xml +11 -0
  19. collective/bbcodesnippets/profiles/uninstall/registry.xml +25 -0
  20. collective/bbcodesnippets/restapi.py +22 -0
  21. collective/bbcodesnippets/setuphandlers.py +21 -0
  22. collective/bbcodesnippets/static/bbcodeicon.png +0 -0
  23. collective/bbcodesnippets/static/bbcodeicon.svg +72 -0
  24. collective/bbcodesnippets/static/collective.bbcodesnippets.js +34 -0
  25. collective/bbcodesnippets/static/collective.bbcodesnippets.js.map +1 -0
  26. collective/bbcodesnippets/static/collective.bbcodesnippets.min.js +1 -0
  27. collective/bbcodesnippets/static/collective.bookmarks.css +2 -0
  28. collective/bbcodesnippets/static/collective.bookmarks.css.map +8 -0
  29. collective/bbcodesnippets/testing.py +34 -0
  30. collective/bbcodesnippets/tests/__init__.py +0 -0
  31. collective/bbcodesnippets/tests/mocks.py +5 -0
  32. collective/bbcodesnippets/tests/test_parser.py +57 -0
  33. collective/bbcodesnippets/tests/test_setup.py +58 -0
  34. collective/bbcodesnippets/tests/test_transform.py +130 -0
  35. collective/bbcodesnippets/tinymce.py +39 -0
  36. collective/bbcodesnippets/transform.py +145 -0
  37. collective/bbcodesnippets/upgrades/2.zcml +27 -0
  38. collective/bbcodesnippets/upgrades/__init__.py +0 -0
  39. collective/bbcodesnippets/upgrades/configure.zcml +5 -0
  40. collective/bbcodesnippets/upgrades/profiles/2/registry.xml +7 -0
  41. collective_bbcodesnippets-2.0.0.dist-info/METADATA +102 -0
  42. collective_bbcodesnippets-2.0.0.dist-info/RECORD +44 -0
  43. collective_bbcodesnippets-2.0.0.dist-info/WHEEL +4 -0
  44. collective_bbcodesnippets-2.0.0.dist-info/licenses/LICENSE.rst +29 -0
@@ -0,0 +1,5 @@
1
+ from . import tinymce # noqa F401
2
+ from zope.i18nmessageid import MessageFactory
3
+
4
+
5
+ _ = MessageFactory("collective.bbcodesnippets")
@@ -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."""