10 # FOR A PARTICULAR PURPOSE. |
10 # FOR A PARTICULAR PURPOSE. |
11 # |
11 # |
12 |
12 |
13 __docformat__ = 'restructuredtext' |
13 __docformat__ = 'restructuredtext' |
14 |
14 |
|
15 import json |
15 |
16 |
16 # import standard library |
17 import tinycss2 |
|
18 from tinycss2.ast import Comment, IdentToken, QualifiedRule, WhitespaceToken |
|
19 from zope.dublincore.interfaces import IZopeDublinCore |
17 |
20 |
18 # import interfaces |
|
19 from pyams_content.zmi import pyams_content |
21 from pyams_content.zmi import pyams_content |
20 from pyams_form.interfaces.form import IForm |
22 from pyams_form.interfaces.form import IForm |
|
23 from pyams_skin.interfaces import ISkinnable |
21 from pyams_skin.interfaces.tinymce import ITinyMCEConfiguration |
24 from pyams_skin.interfaces.tinymce import ITinyMCEConfiguration |
22 from pyams_skin.layer import IPyAMSLayer |
25 from pyams_skin.layer import IPyAMSLayer |
|
26 from pyams_utils.adapter import ContextRequestAdapter, adapter_config |
|
27 from pyams_utils.fanstatic import get_resource_path |
|
28 from pyams_utils.traversing import get_parent |
|
29 from pyams_utils.url import absolute_url |
23 |
30 |
24 # import packages |
31 |
25 from pyams_utils.adapter import adapter_config, ContextRequestAdapter |
32 def parse_css(data): |
26 from pyams_utils.fanstatic import get_resource_path |
33 """Parse given CSS data to extract required styles""" |
|
34 result = [] |
|
35 label = None |
|
36 rules, encoding = tinycss2.parse_stylesheet_bytes(data, skip_whitespace=True) |
|
37 for rule in rules: |
|
38 if isinstance(rule, Comment): |
|
39 label = rule.value.strip() |
|
40 if ':' in label: |
|
41 label, selector = label.split(':', 1) |
|
42 elif isinstance(rule, QualifiedRule): |
|
43 ident_token = class_token = None |
|
44 tokens = iter(rule.prelude) |
|
45 for token in tokens: |
|
46 if isinstance(token, IdentToken): |
|
47 ident_token = token |
|
48 break |
|
49 for token in tokens: |
|
50 if isinstance(token, IdentToken): |
|
51 class_token = token |
|
52 break |
|
53 token_id = ''.join((token.value for token in rule.prelude |
|
54 if not isinstance(token, WhitespaceToken))) |
|
55 result.append({ |
|
56 'title': label or token_id, |
|
57 'classes': class_token.value, |
|
58 'block': ident_token.lower_value if ident_token and |
|
59 (ident_token.lower_value in ('h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'section', 'p', 'div', |
|
60 'blockquote', 'pre')) else None, |
|
61 'inline': ident_token.lower_value if ident_token and |
|
62 (ident_token.lower_value in ('span',)) else None |
|
63 }) |
|
64 label = None |
|
65 return result |
27 |
66 |
28 |
67 |
29 @adapter_config(context=(IForm, IPyAMSLayer), provides=ITinyMCEConfiguration) |
68 @adapter_config(context=(IForm, IPyAMSLayer), provides=ITinyMCEConfiguration) |
30 class TinyMCEEditorConfiguration(ContextRequestAdapter): |
69 class TinyMCEEditorConfiguration(ContextRequestAdapter): |
31 """TinyMCE editor configuration""" |
70 """TinyMCE editor configuration""" |
32 |
71 |
33 @property |
72 @property |
34 def configuration(self): |
73 def configuration(self): |
35 return { |
74 result = { |
36 'ams-plugins': 'pyams_content', |
75 'ams-plugins': 'pyams_content', |
37 'ams-plugin-pyams_content-src': get_resource_path(pyams_content), |
76 'ams-plugin-pyams_content-src': get_resource_path(pyams_content), |
38 'ams-plugin-pyams_content-async': 'false', |
77 'ams-plugin-pyams_content-async': 'false', |
39 'ams-tinymce-init-callback': 'PyAMS_content.TinyMCE.initEditor' |
78 'ams-tinymce-init-callback': 'PyAMS_content.TinyMCE.initEditor' |
40 } |
79 } |
|
80 skinnable = get_parent(self.context.context, ISkinnable) |
|
81 if skinnable is not None: |
|
82 editor_stylesheet = skinnable.editor_stylesheet |
|
83 if editor_stylesheet: |
|
84 modified = IZopeDublinCore(editor_stylesheet).modified |
|
85 result['ams-tinymce-content-css'] = absolute_url(editor_stylesheet, self.request, |
|
86 query={'_': modified.timestamp()}) |
|
87 result['ams-tinymce-editor-styles'] = json.dumps(parse_css(editor_stylesheet.data)) |
|
88 return result |