# HG changeset patch # User Thierry Florac # Date 1593794626 -7200 # Node ID fc32ec8a8f5399b604cbeb2a3278b77e06990f51 # Parent d05fc6aa4217f11d910b925368d95c81b6418f7e Added links to framed paragraphs diff -r d05fc6aa4217 -r fc32ec8a8f53 src/pyams_content/component/paragraph/__init__.py --- a/src/pyams_content/component/paragraph/__init__.py Fri Jul 03 14:42:15 2020 +0200 +++ b/src/pyams_content/component/paragraph/__init__.py Fri Jul 03 18:43:46 2020 +0200 @@ -10,21 +10,20 @@ # FOR A PARTICULAR PURPOSE. # -__docformat__ = 'restructuredtext' - from persistent import Persistent from pyramid.events import subscriber from pyramid.threadlocal import get_current_registry from zope.container.contained import Contained from zope.interface import implementer from zope.lifecycleevent import ObjectModifiedEvent -from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectModifiedEvent, IObjectRemovedEvent +from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectModifiedEvent, \ + IObjectRemovedEvent from zope.schema.fieldproperty import FieldProperty from zope.schema.vocabulary import SimpleTerm, SimpleVocabulary -from pyams_content.component.paragraph.interfaces import CONTENT_PARAGRAPHS_VOCABULARY, IBaseParagraph, \ - IParagraphContainer, IParagraphContainerTarget, IParagraphFactory, IParagraphFactorySettings, IParagraphTitle, \ - PARAGRAPH_FACTORIES_VOCABULARY +from pyams_content.component.paragraph.interfaces import CONTENT_PARAGRAPHS_VOCABULARY, \ + IBaseParagraph, IParagraphContainer, IParagraphContainerTarget, IParagraphFactory, \ + IParagraphFactorySettings, IParagraphTitle, PARAGRAPH_FACTORIES_VOCABULARY from pyams_content.features.checker import BaseContentChecker from pyams_content.features.preview.interfaces import IPreviewTarget from pyams_content.features.renderer import RenderedContentMixin @@ -40,6 +39,9 @@ from pyams_workflow.interfaces import IWorkflowState +__docformat__ = 'restructuredtext' + + # # Auto-creation of default paragraphs # diff -r d05fc6aa4217 -r fc32ec8a8f53 src/pyams_content/component/paragraph/association.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/pyams_content/component/paragraph/association.py Fri Jul 03 18:43:46 2020 +0200 @@ -0,0 +1,104 @@ +# +# Copyright (c) 2015-2020 Thierry Florac +# All Rights Reserved. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE. +# + +"""PyAMS_*** module + +""" + +__docformat__ = 'restructuredtext' + +import re + +from pyquery import PyQuery +from pyramid.threadlocal import get_current_registry +from zope.lifecycleevent import ObjectCreatedEvent + +from pyams_content.component.association import IAssociationContainer +from pyams_content.component.extfile import IBaseExtFile +from pyams_content.component.links import ExternalLink, IExternalLink, IInternalLink, IMailtoLink, \ + InternalLink, MailtoLink +from pyams_i18n.interfaces import II18n +from pyams_sequence.interfaces import ISequentialIntIds +from pyams_utils.registry import get_utility +from pyams_utils.request import check_request +from pyams_utils.url import absolute_url + + +FULL_EMAIL = re.compile('(.*) \<(.*)\>') + + +def check_associations(context, body, lang, notify=True): + """Check for link associations from HTML content""" + associations = IAssociationContainer(context, None) + if associations is None: + return + registry = get_current_registry() + html = PyQuery('{0}'.format(body)) + for link in html('a[href]'): + link_info = None + has_link = False + href = link.attrib['href'] + if href.startswith('oid://'): + sequence = get_utility(ISequentialIntIds) + oid = sequence.get_full_oid(href.split('//', 1)[1]) + for association in associations.values(): + internal_info = IInternalLink(association, None) + if (internal_info is not None) and (internal_info.reference == oid): + has_link = True + break + if not has_link: + link_info = InternalLink() + link_info.visible = False + link_info.reference = oid + link_info.title = {lang: link.attrib.get('title') or link.text} + elif href.startswith('mailto:'): + name = None + email = href[7:] + if '<' in email: + groups = FULL_EMAIL.findall(email) + if groups: + name, email = groups[0] + for association in associations.values(): + mailto_info = IMailtoLink(association, None) + if (mailto_info is not None) and (mailto_info.address == email): + has_link = True + break + if not has_link: + link_info = MailtoLink() + link_info.visible = False + link_info.address = email + link_info.address_name = name or email + link_info.title = {lang: link.attrib.get('title') or link.text} + elif href.startswith('http://') or href.startswith('https://'): + for association in associations.values(): + external_info = IExternalLink(association, None) + if (external_info is not None) and (external_info.url == href): + has_link = True + break + else: + extfile_info = IBaseExtFile(association, None) + if extfile_info is not None: + request = check_request() + extfile_url = absolute_url( + II18n(extfile_info).query_attribute('data', request=request), + request=request) + if extfile_url.endswith(href): + has_link = True + break + if not has_link: + link_info = ExternalLink() + link_info.visible = False + link_info.url = href + link_info.title = {lang: link.attrib.get('title') or link.text} + if link_info is not None: + registry.notify(ObjectCreatedEvent(link_info)) + associations.append(link_info, notify=notify) \ No newline at end of file diff -r d05fc6aa4217 -r fc32ec8a8f53 src/pyams_content/component/paragraph/frame.py --- a/src/pyams_content/component/paragraph/frame.py Fri Jul 03 14:42:15 2020 +0200 +++ b/src/pyams_content/component/paragraph/frame.py Fri Jul 03 18:43:46 2020 +0200 @@ -12,17 +12,22 @@ __docformat__ = 'restructuredtext' +from pyramid.events import subscriber from zope.interface import implementer +from zope.lifecycleevent import IObjectAddedEvent, IObjectModifiedEvent from zope.schema.fieldproperty import FieldProperty from pyams_content.component.extfile.interfaces import IExtFileContainerTarget from pyams_content.component.illustration.interfaces import IIllustrationTarget from pyams_content.component.links.interfaces import ILinkContainerTarget -from pyams_content.component.paragraph import BaseParagraph, BaseParagraphContentChecker, BaseParagraphFactory +from pyams_content.component.paragraph import BaseParagraph, BaseParagraphContentChecker, \ + BaseParagraphFactory +from pyams_content.component.paragraph.association import check_associations from pyams_content.component.paragraph.interfaces import IParagraphFactory -from pyams_content.component.paragraph.interfaces.frame import FRAME_PARAGRAPH_NAME, FRAME_PARAGRAPH_RENDERERS, \ - FRAME_PARAGRAPH_TYPE, IFrameParagraph -from pyams_content.features.checker.interfaces import IContentChecker, MISSING_LANG_VALUE, MISSING_VALUE +from pyams_content.component.paragraph.interfaces.frame import FRAME_PARAGRAPH_NAME, \ + FRAME_PARAGRAPH_RENDERERS, FRAME_PARAGRAPH_TYPE, IFrameParagraph +from pyams_content.features.checker.interfaces import IContentChecker, MISSING_LANG_VALUE, \ + MISSING_VALUE from pyams_content.features.renderer import RenderersVocabulary from pyams_i18n.interfaces import II18n, II18nManager, INegotiator from pyams_utils.adapter import adapter_config @@ -88,3 +93,20 @@ """Framed text paragraph renderers vocabulary""" content_interface = IFrameParagraph + + +@subscriber(IObjectAddedEvent, context_selector=IFrameParagraph) +def handle_added_frame_paragraph(event): + """Check for new associations from added paragraph""" + paragraph = event.object + for lang, body in (paragraph.body or {}).items(): + check_associations(paragraph, body, lang, notify=False) + + +@subscriber(IObjectModifiedEvent, context_selector=IFrameParagraph) +def handle_modified_frame_paragraph(event): + """Check for new associations from modified paragraph""" + paragraph = event.object + for lang, body in (paragraph.body or {}).items(): + check_associations(paragraph, body, lang, notify=False) + diff -r d05fc6aa4217 -r fc32ec8a8f53 src/pyams_content/component/paragraph/html.py --- a/src/pyams_content/component/paragraph/html.py Fri Jul 03 14:42:15 2020 +0200 +++ b/src/pyams_content/component/paragraph/html.py Fri Jul 03 18:43:46 2020 +0200 @@ -10,24 +10,17 @@ # FOR A PARTICULAR PURPOSE. # -import re - -from pyquery import PyQuery from pyramid.events import subscriber -from pyramid.threadlocal import get_current_registry from zope.interface import implementer -from zope.lifecycleevent import ObjectCreatedEvent from zope.lifecycleevent.interfaces import IObjectAddedEvent, IObjectModifiedEvent from zope.schema.fieldproperty import FieldProperty -from pyams_content.component.association.interfaces import IAssociationContainer -from pyams_content.component.extfile.interfaces import IBaseExtFile, IExtFileContainerTarget +from pyams_content.component.extfile.interfaces import IExtFileContainerTarget from pyams_content.component.illustration.interfaces import IIllustrationTarget -from pyams_content.component.links import ExternalLink, InternalLink, MailtoLink -from pyams_content.component.links.interfaces import IExternalLink, IInternalLink, \ - ILinkContainerTarget, IMailtoLink +from pyams_content.component.links.interfaces import ILinkContainerTarget from pyams_content.component.paragraph import BaseParagraph, BaseParagraphContentChecker, \ BaseParagraphFactory +from pyams_content.component.paragraph.association import check_associations from pyams_content.component.paragraph.interfaces import IParagraphFactory from pyams_content.component.paragraph.interfaces.html import HTML_PARAGRAPH_NAME, \ HTML_PARAGRAPH_RENDERERS, HTML_PARAGRAPH_TYPE, IHTMLParagraph, IRawParagraph, \ @@ -36,13 +29,10 @@ MISSING_VALUE from pyams_content.features.renderer import RenderersVocabulary from pyams_i18n.interfaces import II18n, II18nManager, INegotiator -from pyams_sequence.interfaces import ISequentialIntIds from pyams_utils.adapter import adapter_config from pyams_utils.factory import factory_config from pyams_utils.registry import get_utility, utility_config -from pyams_utils.request import check_request from pyams_utils.traversing import get_parent -from pyams_utils.url import absolute_url from pyams_utils.vocabulary import vocabulary_config @@ -129,75 +119,6 @@ content_type = HTMLParagraph -FULL_EMAIL = re.compile('(.*) \<(.*)\>') - - -def check_associations(context, body, lang, notify=True): - """Check for link associations from HTML content""" - registry = get_current_registry() - associations = IAssociationContainer(context) - html = PyQuery('{0}'.format(body)) - for link in html('a[href]'): - link_info = None - has_link = False - href = link.attrib['href'] - if href.startswith('oid://'): - sequence = get_utility(ISequentialIntIds) - oid = sequence.get_full_oid(href.split('//', 1)[1]) - for association in associations.values(): - internal_info = IInternalLink(association, None) - if (internal_info is not None) and (internal_info.reference == oid): - has_link = True - break - if not has_link: - link_info = InternalLink() - link_info.visible = False - link_info.reference = oid - link_info.title = {lang: link.attrib.get('title') or link.text} - elif href.startswith('mailto:'): - name = None - email = href[7:] - if '<' in email: - groups = FULL_EMAIL.findall(email) - if groups: - name, email = groups[0] - for association in associations.values(): - mailto_info = IMailtoLink(association, None) - if (mailto_info is not None) and (mailto_info.address == email): - has_link = True - break - if not has_link: - link_info = MailtoLink() - link_info.visible = False - link_info.address = email - link_info.address_name = name or email - link_info.title = {lang: link.attrib.get('title') or link.text} - elif href.startswith('http://') or href.startswith('https://'): - for association in associations.values(): - external_info = IExternalLink(association, None) - if (external_info is not None) and (external_info.url == href): - has_link = True - break - else: - extfile_info = IBaseExtFile(association, None) - if extfile_info is not None: - request = check_request() - extfile_url = absolute_url( - II18n(extfile_info).query_attribute('data', request=request), - request=request) - if extfile_url.endswith(href): - has_link = True - break - if not has_link: - link_info = ExternalLink() - link_info.visible = False - link_info.url = href - link_info.title = {lang: link.attrib.get('title') or link.text} - if link_info is not None: - registry.notify(ObjectCreatedEvent(link_info)) - associations.append(link_info, notify=notify) - - @subscriber(IObjectAddedEvent, context_selector=IHTMLParagraph) def handle_added_html_paragraph(event): """Check for new associations from added paragraph""" diff -r d05fc6aa4217 -r fc32ec8a8f53 src/pyams_content/component/paragraph/zmi/frame.py --- a/src/pyams_content/component/paragraph/zmi/frame.py Fri Jul 03 14:42:15 2020 +0200 +++ b/src/pyams_content/component/paragraph/zmi/frame.py Fri Jul 03 18:43:46 2020 +0200 @@ -10,8 +10,6 @@ # FOR A PARTICULAR PURPOSE. # -__docformat__ = 'restructuredtext' - from z3c.form import button from z3c.form.interfaces import INPUT_MODE from zope.interface import Interface, implementer @@ -20,15 +18,20 @@ from pyams_content.component.association.zmi import AssociationsTable from pyams_content.component.association.zmi.interfaces import IAssociationsParentForm from pyams_content.component.paragraph.frame import FrameParagraph -from pyams_content.component.paragraph.interfaces import IBaseParagraph, IParagraphContainerTarget, IParagraphTitle -from pyams_content.component.paragraph.interfaces.frame import FRAME_PARAGRAPH_TYPE, IFrameParagraph -from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, BaseParagraphAJAXEditForm, \ - BaseParagraphAddForm, BaseParagraphAddMenu, BaseParagraphPropertiesEditForm, IParagraphInnerEditFormButtons, \ +from pyams_content.component.paragraph.interfaces import IBaseParagraph, \ + IParagraphContainerTarget, IParagraphTitle +from pyams_content.component.paragraph.interfaces.frame import FRAME_PARAGRAPH_TYPE, \ + IFrameParagraph +from pyams_content.component.paragraph.zmi import BaseParagraphAJAXAddForm, \ + BaseParagraphAJAXEditForm, BaseParagraphAddForm, BaseParagraphAddMenu, \ + BaseParagraphPropertiesEditForm, IParagraphInnerEditFormButtons, \ get_json_paragraph_refresh_event, get_json_paragraph_toolbar_refresh_event from pyams_content.component.paragraph.zmi.container import ParagraphContainerTable, \ ParagraphTitleToolbarViewletManager -from pyams_content.component.paragraph.zmi.interfaces import IParagraphContainerView, IParagraphInnerEditor +from pyams_content.component.paragraph.zmi.interfaces import IParagraphContainerView, \ + IParagraphInnerEditor from pyams_content.interfaces import MANAGE_CONTENT_PERMISSION +from pyams_content.zmi import pyams_content from pyams_form.form import ajax_config from pyams_form.interfaces.form import IInnerForm from pyams_i18n.interfaces import II18n @@ -38,12 +41,16 @@ from pyams_skin.interfaces.viewlet import IToolbarAddingMenu from pyams_skin.layer import IPyAMSLayer from pyams_utils.adapter import ContextRequestAdapter, adapter_config +from pyams_utils.fanstatic import get_resource_path from pyams_utils.html import html_to_text from pyams_utils.text import get_text_start from pyams_utils.traversing import get_parent from pyams_viewlet.viewlet import viewlet_config from pyams_zmi.interfaces import IPropertiesEditForm + +__docformat__ = 'restructuredtext' + from pyams_content import _ @@ -60,9 +67,14 @@ """Custom configuration for 'body' widget editor""" configuration = { + 'ams-plugins': 'pyams_content', + 'ams-plugin-pyams_content-src': get_resource_path(pyams_content), + 'ams-plugin-pyams_content-async': 'false', + 'ams-tinymce-init-callback': 'PyAMS_content.TinyMCE.initEditor', 'ams-tinymce-menubar': False, - 'ams-tinymce-plugins': ['paste', 'lists'], - 'ams-tinymce-toolbar': 'undo redo | pastetext | bold italic superscript | bullist numlist', + 'ams-tinymce-plugins': ['paste', 'lists', 'charmap', 'internal_links', 'link'], + 'ams-tinymce-toolbar': 'undo redo | pastetext | bold italic superscript | ' + 'bullist numlist | charmap internal_links link', 'ams-tinymce-toolbar1': False, 'ams-tinymce-toolbar2': False, 'ams-tinymce-height': 150 @@ -149,10 +161,12 @@ parent = get_parent(self.context, IAssociationContainerTarget) output.setdefault('events', []).append( get_json_paragraph_toolbar_refresh_event(parent, self.request, - ParagraphContainerTable, ParagraphTitleToolbarViewletManager)) + ParagraphContainerTable, + ParagraphTitleToolbarViewletManager)) # refresh associations table output.setdefault('events', []).append( - get_json_switched_table_refresh_event(self.context, self.request, AssociationsTable)) + get_json_switched_table_refresh_event(self.context, self.request, + AssociationsTable)) return output