Show More
@@ -26,7 +26,8 b' import hashlib' | |||||
26 | from rhodecode.lib.ext_json import json |
|
26 | from rhodecode.lib.ext_json import json | |
27 | from rhodecode.apps.file_store import utils |
|
27 | from rhodecode.apps.file_store import utils | |
28 | from rhodecode.apps.file_store.extensions import resolve_extensions |
|
28 | from rhodecode.apps.file_store.extensions import resolve_extensions | |
29 |
from rhodecode.apps.file_store.exceptions import |
|
29 | from rhodecode.apps.file_store.exceptions import ( | |
|
30 | FileNotAllowedException, FileOverSizeException) | |||
30 |
|
31 | |||
31 | METADATA_VER = 'v1' |
|
32 | METADATA_VER = 'v1' | |
32 |
|
33 | |||
@@ -157,7 +158,7 b' class LocalFileStorage(object):' | |||||
157 | return ext in [normalize_ext(x) for x in extensions] |
|
158 | return ext in [normalize_ext(x) for x in extensions] | |
158 |
|
159 | |||
159 | def save_file(self, file_obj, filename, directory=None, extensions=None, |
|
160 | def save_file(self, file_obj, filename, directory=None, extensions=None, | |
160 | extra_metadata=None, **kwargs): |
|
161 | extra_metadata=None, max_filesize=None, **kwargs): | |
161 | """ |
|
162 | """ | |
162 | Saves a file object to the uploads location. |
|
163 | Saves a file object to the uploads location. | |
163 | Returns the resolved filename, i.e. the directory + |
|
164 | Returns the resolved filename, i.e. the directory + | |
@@ -167,7 +168,9 b' class LocalFileStorage(object):' | |||||
167 | :param filename: original filename |
|
168 | :param filename: original filename | |
168 | :param directory: relative path of sub-directory |
|
169 | :param directory: relative path of sub-directory | |
169 | :param extensions: iterable of allowed extensions, if not default |
|
170 | :param extensions: iterable of allowed extensions, if not default | |
|
171 | :param max_filesize: maximum size of file that should be allowed | |||
170 | :param extra_metadata: extra JSON metadata to store next to the file with .meta suffix |
|
172 | :param extra_metadata: extra JSON metadata to store next to the file with .meta suffix | |
|
173 | ||||
171 | """ |
|
174 | """ | |
172 |
|
175 | |||
173 | extensions = extensions or self.extensions |
|
176 | extensions = extensions or self.extensions | |
@@ -199,6 +202,12 b' class LocalFileStorage(object):' | |||||
199 | metadata = extra_metadata |
|
202 | metadata = extra_metadata | |
200 |
|
203 | |||
201 | size = os.stat(path).st_size |
|
204 | size = os.stat(path).st_size | |
|
205 | ||||
|
206 | if max_filesize and size > max_filesize: | |||
|
207 | # free up the copied file, and raise exc | |||
|
208 | os.remove(path) | |||
|
209 | raise FileOverSizeException() | |||
|
210 | ||||
202 | file_hash = self.calculate_path_hash(path) |
|
211 | file_hash = self.calculate_path_hash(path) | |
203 |
|
212 | |||
204 | metadata.update( |
|
213 | metadata.update( |
@@ -79,6 +79,10 b' def includeme(config):' | |||||
79 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True) |
|
79 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/preview', repo_route=True) | |
80 |
|
80 | |||
81 | config.add_route( |
|
81 | config.add_route( | |
|
82 | name='repo_commit_comment_attachment_upload', | |||
|
83 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/attachment_upload', repo_route=True) | |||
|
84 | ||||
|
85 | config.add_route( | |||
82 | name='repo_commit_comment_delete', |
|
86 | name='repo_commit_comment_delete', | |
83 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True) |
|
87 | pattern='/{repo_name:.*?[^/]}/changeset/{commit_id}/comment/{comment_id}/delete', repo_route=True) | |
84 |
|
88 |
@@ -28,6 +28,8 b' from pyramid.renderers import render' | |||||
28 | from pyramid.response import Response |
|
28 | from pyramid.response import Response | |
29 |
|
29 | |||
30 | from rhodecode.apps._base import RepoAppView |
|
30 | from rhodecode.apps._base import RepoAppView | |
|
31 | from rhodecode.apps.file_store import utils as store_utils | |||
|
32 | from rhodecode.apps.file_store.exceptions import FileNotAllowedException, FileOverSizeException | |||
31 |
|
33 | |||
32 | from rhodecode.lib import diffs, codeblocks |
|
34 | from rhodecode.lib import diffs, codeblocks | |
33 | from rhodecode.lib.auth import ( |
|
35 | from rhodecode.lib.auth import ( | |
@@ -43,7 +45,7 b' from rhodecode.lib.utils2 import safe_un' | |||||
43 | from rhodecode.lib.vcs.backends.base import EmptyCommit |
|
45 | from rhodecode.lib.vcs.backends.base import EmptyCommit | |
44 | from rhodecode.lib.vcs.exceptions import ( |
|
46 | from rhodecode.lib.vcs.exceptions import ( | |
45 | RepositoryError, CommitDoesNotExistError) |
|
47 | RepositoryError, CommitDoesNotExistError) | |
46 | from rhodecode.model.db import ChangesetComment, ChangesetStatus |
|
48 | from rhodecode.model.db import ChangesetComment, ChangesetStatus, FileStore | |
47 | from rhodecode.model.changeset_status import ChangesetStatusModel |
|
49 | from rhodecode.model.changeset_status import ChangesetStatusModel | |
48 | from rhodecode.model.comment import CommentsModel |
|
50 | from rhodecode.model.comment import CommentsModel | |
49 | from rhodecode.model.meta import Session |
|
51 | from rhodecode.model.meta import Session | |
@@ -423,6 +425,101 b' class RepoCommitsView(RepoAppView):' | |||||
423 | 'repository.read', 'repository.write', 'repository.admin') |
|
425 | 'repository.read', 'repository.write', 'repository.admin') | |
424 | @CSRFRequired() |
|
426 | @CSRFRequired() | |
425 | @view_config( |
|
427 | @view_config( | |
|
428 | route_name='repo_commit_comment_attachment_upload', request_method='POST', | |||
|
429 | renderer='json_ext', xhr=True) | |||
|
430 | def repo_commit_comment_attachment_upload(self): | |||
|
431 | c = self.load_default_context() | |||
|
432 | upload_key = 'attachment' | |||
|
433 | ||||
|
434 | file_obj = self.request.POST.get(upload_key) | |||
|
435 | ||||
|
436 | if file_obj is None: | |||
|
437 | self.request.response.status = 400 | |||
|
438 | return {'store_fid': None, | |||
|
439 | 'access_path': None, | |||
|
440 | 'error': '{} data field is missing'.format(upload_key)} | |||
|
441 | ||||
|
442 | if not hasattr(file_obj, 'filename'): | |||
|
443 | self.request.response.status = 400 | |||
|
444 | return {'store_fid': None, | |||
|
445 | 'access_path': None, | |||
|
446 | 'error': 'filename cannot be read from the data field'} | |||
|
447 | ||||
|
448 | filename = file_obj.filename | |||
|
449 | file_display_name = filename | |||
|
450 | ||||
|
451 | metadata = { | |||
|
452 | 'user_uploaded': {'username': self._rhodecode_user.username, | |||
|
453 | 'user_id': self._rhodecode_user.user_id, | |||
|
454 | 'ip': self._rhodecode_user.ip_addr}} | |||
|
455 | ||||
|
456 | # TODO(marcink): allow .ini configuration for allowed_extensions, and file-size | |||
|
457 | allowed_extensions = [ | |||
|
458 | 'gif', '.jpeg', '.jpg', '.png', '.docx', '.gz', '.log', '.pdf', | |||
|
459 | '.pptx', '.txt', '.xlsx', '.zip'] | |||
|
460 | max_file_size = 10 * 1024 * 1024 # 10MB, also validated via dropzone.js | |||
|
461 | ||||
|
462 | try: | |||
|
463 | storage = store_utils.get_file_storage(self.request.registry.settings) | |||
|
464 | store_uid, metadata = storage.save_file( | |||
|
465 | file_obj.file, filename, extra_metadata=metadata, | |||
|
466 | extensions=allowed_extensions, max_filesize=max_file_size) | |||
|
467 | except FileNotAllowedException: | |||
|
468 | self.request.response.status = 400 | |||
|
469 | permitted_extensions = ', '.join(allowed_extensions) | |||
|
470 | error_msg = 'File `{}` is not allowed. ' \ | |||
|
471 | 'Only following extensions are permitted: {}'.format( | |||
|
472 | filename, permitted_extensions) | |||
|
473 | return {'store_fid': None, | |||
|
474 | 'access_path': None, | |||
|
475 | 'error': error_msg} | |||
|
476 | except FileOverSizeException: | |||
|
477 | self.request.response.status = 400 | |||
|
478 | limit_mb = h.format_byte_size_binary(max_file_size) | |||
|
479 | return {'store_fid': None, | |||
|
480 | 'access_path': None, | |||
|
481 | 'error': 'File {} is exceeding allowed limit of {}.'.format( | |||
|
482 | filename, limit_mb)} | |||
|
483 | ||||
|
484 | try: | |||
|
485 | entry = FileStore.create( | |||
|
486 | file_uid=store_uid, filename=metadata["filename"], | |||
|
487 | file_hash=metadata["sha256"], file_size=metadata["size"], | |||
|
488 | file_display_name=file_display_name, | |||
|
489 | file_description=u'comment attachment `{}`'.format(safe_unicode(filename)), | |||
|
490 | hidden=True, check_acl=True, user_id=self._rhodecode_user.user_id, | |||
|
491 | scope_repo_id=self.db_repo.repo_id | |||
|
492 | ) | |||
|
493 | Session().add(entry) | |||
|
494 | Session().commit() | |||
|
495 | log.debug('Stored upload in DB as %s', entry) | |||
|
496 | except Exception: | |||
|
497 | log.exception('Failed to store file %s', filename) | |||
|
498 | self.request.response.status = 400 | |||
|
499 | return {'store_fid': None, | |||
|
500 | 'access_path': None, | |||
|
501 | 'error': 'File {} failed to store in DB.'.format(filename)} | |||
|
502 | ||||
|
503 | Session().commit() | |||
|
504 | ||||
|
505 | return { | |||
|
506 | 'store_fid': store_uid, | |||
|
507 | 'access_path': h.route_path( | |||
|
508 | 'download_file', fid=store_uid), | |||
|
509 | 'fqn_access_path': h.route_url( | |||
|
510 | 'download_file', fid=store_uid), | |||
|
511 | 'repo_access_path': h.route_path( | |||
|
512 | 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid), | |||
|
513 | 'repo_fqn_access_path': h.route_url( | |||
|
514 | 'repo_artifacts_get', repo_name=self.db_repo_name, uid=store_uid), | |||
|
515 | } | |||
|
516 | ||||
|
517 | @LoginRequired() | |||
|
518 | @NotAnonymous() | |||
|
519 | @HasRepoPermissionAnyDecorator( | |||
|
520 | 'repository.read', 'repository.write', 'repository.admin') | |||
|
521 | @CSRFRequired() | |||
|
522 | @view_config( | |||
426 | route_name='repo_commit_comment_delete', request_method='POST', |
|
523 | route_name='repo_commit_comment_delete', request_method='POST', | |
427 | renderer='json_ext') |
|
524 | renderer='json_ext') | |
428 | def repo_commit_comment_delete(self): |
|
525 | def repo_commit_comment_delete(self): |
@@ -5115,7 +5115,7 b' class FileStore(Base, BaseModel):' | |||||
5115 | # if repo/repo_group reference is set, check for permissions |
|
5115 | # if repo/repo_group reference is set, check for permissions | |
5116 | check_acl = Column('check_acl', Boolean(), nullable=False, default=True) |
|
5116 | check_acl = Column('check_acl', Boolean(), nullable=False, default=True) | |
5117 |
|
5117 | |||
5118 |
# hidden defines an attach |
|
5118 | # hidden defines an attachment that should be hidden from showing in artifact listing | |
5119 | hidden = Column('hidden', Boolean(), nullable=False, default=False) |
|
5119 | hidden = Column('hidden', Boolean(), nullable=False, default=False) | |
5120 |
|
5120 | |||
5121 | user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) |
|
5121 | user_id = Column('user_id', Integer(), ForeignKey('users.user_id'), nullable=False) | |
@@ -5147,8 +5147,8 b' class FileStore(Base, BaseModel):' | |||||
5147 |
|
5147 | |||
5148 | @classmethod |
|
5148 | @classmethod | |
5149 | def create(cls, file_uid, filename, file_hash, file_size, file_display_name='', |
|
5149 | def create(cls, file_uid, filename, file_hash, file_size, file_display_name='', | |
5150 |
file_description='', enabled=True, check_acl=True, |
|
5150 | file_description='', enabled=True, hidden=False, check_acl=True, | |
5151 | scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None): |
|
5151 | user_id=None, scope_user_id=None, scope_repo_id=None, scope_repo_group_id=None): | |
5152 |
|
5152 | |||
5153 | store_entry = FileStore() |
|
5153 | store_entry = FileStore() | |
5154 | store_entry.file_uid = file_uid |
|
5154 | store_entry.file_uid = file_uid | |
@@ -5160,11 +5160,13 b' class FileStore(Base, BaseModel):' | |||||
5160 |
|
5160 | |||
5161 | store_entry.check_acl = check_acl |
|
5161 | store_entry.check_acl = check_acl | |
5162 | store_entry.enabled = enabled |
|
5162 | store_entry.enabled = enabled | |
|
5163 | store_entry.hidden = hidden | |||
5163 |
|
5164 | |||
5164 | store_entry.user_id = user_id |
|
5165 | store_entry.user_id = user_id | |
5165 | store_entry.scope_user_id = scope_user_id |
|
5166 | store_entry.scope_user_id = scope_user_id | |
5166 | store_entry.scope_repo_id = scope_repo_id |
|
5167 | store_entry.scope_repo_id = scope_repo_id | |
5167 | store_entry.scope_repo_group_id = scope_repo_group_id |
|
5168 | store_entry.scope_repo_group_id = scope_repo_group_id | |
|
5169 | ||||
5168 | return store_entry |
|
5170 | return store_entry | |
5169 |
|
5171 | |||
5170 | @classmethod |
|
5172 | @classmethod |
@@ -539,13 +539,40 b' form.comment-form {' | |||||
539 | } |
|
539 | } | |
540 |
|
540 | |||
541 | .comment-area-footer { |
|
541 | .comment-area-footer { | |
542 | display: flex; |
|
542 | min-height: 30px; | |
543 | } |
|
543 | } | |
544 |
|
544 | |||
545 | .comment-footer .toolbar { |
|
545 | .comment-footer .toolbar { | |
546 |
|
546 | |||
547 | } |
|
547 | } | |
548 |
|
548 | |||
|
549 | .comment-attachment-uploader { | |||
|
550 | border: 1px dashed white; | |||
|
551 | border-radius: @border-radius; | |||
|
552 | margin-top: -10px; | |||
|
553 | ||||
|
554 | &.dz-drag-hover { | |||
|
555 | border-color: @grey3; | |||
|
556 | } | |||
|
557 | ||||
|
558 | .dz-error-message { | |||
|
559 | padding-top: 0; | |||
|
560 | } | |||
|
561 | } | |||
|
562 | ||||
|
563 | .comment-attachment-text { | |||
|
564 | clear: both; | |||
|
565 | font-size: 11px; | |||
|
566 | color: #8F8F8F; | |||
|
567 | width: 100%; | |||
|
568 | .pick-attachment { | |||
|
569 | color: #8F8F8F; | |||
|
570 | } | |||
|
571 | .pick-attachment:hover { | |||
|
572 | color: @rcblue; | |||
|
573 | } | |||
|
574 | } | |||
|
575 | ||||
549 | .nav-links { |
|
576 | .nav-links { | |
550 | padding: 0; |
|
577 | padding: 0; | |
551 | margin: 0; |
|
578 | margin: 0; | |
@@ -579,7 +606,6 b' form.comment-form {' | |||||
579 |
|
606 | |||
580 | .toolbar-text { |
|
607 | .toolbar-text { | |
581 | float: left; |
|
608 | float: left; | |
582 | margin: -5px 0px 0px 0px; |
|
|||
583 | font-size: 12px; |
|
609 | font-size: 12px; | |
584 | } |
|
610 | } | |
585 |
|
611 |
@@ -204,6 +204,7 b' div.markdown-block img {' | |||||
204 | border-style: none; |
|
204 | border-style: none; | |
205 | background-color: #fff; |
|
205 | background-color: #fff; | |
206 | padding-right: 20px; |
|
206 | padding-right: 20px; | |
|
207 | max-width: 100%; | |||
207 | } |
|
208 | } | |
208 |
|
209 | |||
209 |
|
210 |
@@ -118,6 +118,10 b' table.dataTable {' | |||||
118 | } |
|
118 | } | |
119 | } |
|
119 | } | |
120 |
|
120 | |||
|
121 | &.td-sha { | |||
|
122 | white-space: nowrap; | |||
|
123 | } | |||
|
124 | ||||
121 | &.td-graphbox { |
|
125 | &.td-graphbox { | |
122 | width: 100px; |
|
126 | width: 100px; | |
123 | max-width: 100px; |
|
127 | max-width: 100px; |
@@ -174,6 +174,7 b' function registerRCRoutes() {' | |||||
174 | pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']); |
|
174 | pyroutes.register('repo_commit_data', '/%(repo_name)s/changeset-data/%(commit_id)s', ['repo_name', 'commit_id']); | |
175 | pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']); |
|
175 | pyroutes.register('repo_commit_comment_create', '/%(repo_name)s/changeset/%(commit_id)s/comment/create', ['repo_name', 'commit_id']); | |
176 | pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']); |
|
176 | pyroutes.register('repo_commit_comment_preview', '/%(repo_name)s/changeset/%(commit_id)s/comment/preview', ['repo_name', 'commit_id']); | |
|
177 | pyroutes.register('repo_commit_comment_attachment_upload', '/%(repo_name)s/changeset/%(commit_id)s/comment/attachment_upload', ['repo_name', 'commit_id']); | |||
177 | pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']); |
|
178 | pyroutes.register('repo_commit_comment_delete', '/%(repo_name)s/changeset/%(commit_id)s/comment/%(comment_id)s/delete', ['repo_name', 'commit_id', 'comment_id']); | |
178 | pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']); |
|
179 | pyroutes.register('repo_commit_raw_deprecated', '/%(repo_name)s/raw-changeset/%(commit_id)s', ['repo_name', 'commit_id']); | |
179 | pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); |
|
180 | pyroutes.register('repo_archivefile', '/%(repo_name)s/archive/%(fname)s', ['repo_name', 'fname']); |
@@ -654,6 +654,95 b' var CommentsController = function() {' | |||||
654 | }, 100); |
|
654 | }, 100); | |
655 | } |
|
655 | } | |
656 |
|
656 | |||
|
657 | // add dropzone support | |||
|
658 | var insertAttachmentText = function (cm, attachmentName, attachmentStoreUrl, isRendered) { | |||
|
659 | var renderer = templateContext.visual.default_renderer; | |||
|
660 | if (renderer == 'rst') { | |||
|
661 | var attachmentUrl = '`#{0} <{1}>`_'.format(attachmentName, attachmentStoreUrl); | |||
|
662 | if (isRendered){ | |||
|
663 | attachmentUrl = '\n.. image:: {0}'.format(attachmentStoreUrl); | |||
|
664 | } | |||
|
665 | } else if (renderer == 'markdown') { | |||
|
666 | var attachmentUrl = '[{0}]({1})'.format(attachmentName, attachmentStoreUrl); | |||
|
667 | if (isRendered){ | |||
|
668 | attachmentUrl = '!' + attachmentUrl; | |||
|
669 | } | |||
|
670 | } else { | |||
|
671 | var attachmentUrl = '{}'.format(attachmentStoreUrl); | |||
|
672 | } | |||
|
673 | cm.replaceRange(attachmentUrl+'\n', CodeMirror.Pos(cm.lastLine())); | |||
|
674 | ||||
|
675 | return false; | |||
|
676 | }; | |||
|
677 | ||||
|
678 | //see: https://www.dropzonejs.com/#configuration | |||
|
679 | var storeUrl = pyroutes.url('repo_commit_comment_attachment_upload', | |||
|
680 | {'repo_name': templateContext.repo_name, | |||
|
681 | 'commit_id': templateContext.commit_data.commit_id}) | |||
|
682 | ||||
|
683 | var previewTmpl = $(formElement).find('.comment-attachment-uploader-template').get(0).innerHTML; | |||
|
684 | var selectLink = $(formElement).find('.pick-attachment').get(0); | |||
|
685 | $(formElement).find('.comment-attachment-uploader').dropzone({ | |||
|
686 | url: storeUrl, | |||
|
687 | headers: {"X-CSRF-Token": CSRF_TOKEN}, | |||
|
688 | paramName: function () { | |||
|
689 | return "attachment" | |||
|
690 | }, // The name that will be used to transfer the file | |||
|
691 | clickable: selectLink, | |||
|
692 | parallelUploads: 1, | |||
|
693 | maxFiles: 10, | |||
|
694 | maxFilesize: templateContext.attachment_store.max_file_size_mb, | |||
|
695 | uploadMultiple: false, | |||
|
696 | autoProcessQueue: true, // if false queue will not be processed automatically. | |||
|
697 | createImageThumbnails: false, | |||
|
698 | previewTemplate: previewTmpl, | |||
|
699 | ||||
|
700 | accept: function (file, done) { | |||
|
701 | done(); | |||
|
702 | }, | |||
|
703 | init: function () { | |||
|
704 | ||||
|
705 | this.on("sending", function (file, xhr, formData) { | |||
|
706 | $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').hide(); | |||
|
707 | $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').show(); | |||
|
708 | }); | |||
|
709 | ||||
|
710 | this.on("success", function (file, response) { | |||
|
711 | $(formElement).find('.comment-attachment-uploader').find('.dropzone-text').show(); | |||
|
712 | $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide(); | |||
|
713 | ||||
|
714 | var isRendered = false; | |||
|
715 | var ext = file.name.split('.').pop(); | |||
|
716 | var imageExts = templateContext.attachment_store.image_ext; | |||
|
717 | if (imageExts.indexOf(ext) !== -1){ | |||
|
718 | isRendered = true; | |||
|
719 | } | |||
|
720 | ||||
|
721 | insertAttachmentText(cm, file.name, response.repo_fqn_access_path, isRendered) | |||
|
722 | }); | |||
|
723 | ||||
|
724 | this.on("error", function (file, errorMessage, xhr) { | |||
|
725 | $(formElement).find('.comment-attachment-uploader').find('.dropzone-upload').hide(); | |||
|
726 | ||||
|
727 | var error = null; | |||
|
728 | ||||
|
729 | if (xhr !== undefined){ | |||
|
730 | var httpStatus = xhr.status + " " + xhr.statusText; | |||
|
731 | if (xhr.status >= 500) { | |||
|
732 | error = httpStatus; | |||
|
733 | } | |||
|
734 | } | |||
|
735 | ||||
|
736 | if (error === null) { | |||
|
737 | error = errorMessage.error || errorMessage || httpStatus; | |||
|
738 | } | |||
|
739 | $(file.previewElement).find('.dz-error-message').html('ERROR: {0}'.format(error)); | |||
|
740 | ||||
|
741 | }); | |||
|
742 | } | |||
|
743 | }); | |||
|
744 | ||||
|
745 | ||||
657 | return commentForm; |
|
746 | return commentForm; | |
658 | }; |
|
747 | }; | |
659 |
|
748 |
@@ -34,6 +34,11 b" c.template_context['search_context'] = {" | |||||
34 | 'repo_view_type': c.template_context.get('repo_view_type'), |
|
34 | 'repo_view_type': c.template_context.get('repo_view_type'), | |
35 | } |
|
35 | } | |
36 |
|
36 | |||
|
37 | c.template_context['attachment_store'] = { | |||
|
38 | 'max_file_size_mb': 10, | |||
|
39 | 'image_ext': ["png", "jpg", "gif", "jpeg"] | |||
|
40 | } | |||
|
41 | ||||
37 | %> |
|
42 | %> | |
38 | <html xmlns="http://www.w3.org/1999/xhtml"> |
|
43 | <html xmlns="http://www.w3.org/1999/xhtml"> | |
39 | <head> |
|
44 | <head> |
@@ -257,7 +257,6 b'' | |||||
257 | }); |
|
257 | }); | |
258 | % endif |
|
258 | % endif | |
259 |
|
259 | |||
260 |
|
||||
261 | </script> |
|
260 | </script> | |
262 | % else: |
|
261 | % else: | |
263 | ## form state when not logged in |
|
262 | ## form state when not logged in | |
@@ -309,8 +308,8 b'' | |||||
309 |
|
308 | |||
310 |
|
309 | |||
311 | <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)"> |
|
310 | <%def name="comment_form(form_type, form_id='', lineno_id='{1}', review_statuses=None, form_extras=None)"> | |
|
311 | ||||
312 | ## comment injected based on assumption that user is logged in |
|
312 | ## comment injected based on assumption that user is logged in | |
313 |
|
||||
314 | <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET"> |
|
313 | <form ${'id="{}"'.format(form_id) if form_id else '' |n} action="#" method="GET"> | |
315 |
|
314 | |||
316 | <div class="comment-area"> |
|
315 | <div class="comment-area"> | |
@@ -341,7 +340,7 b'' | |||||
341 | </div> |
|
340 | </div> | |
342 | </div> |
|
341 | </div> | |
343 |
|
342 | |||
344 | <div class="comment-area-footer"> |
|
343 | <div class="comment-area-footer comment-attachment-uploader"> | |
345 | <div class="toolbar"> |
|
344 | <div class="toolbar"> | |
346 | <div class="toolbar-text"> |
|
345 | <div class="toolbar-text"> | |
347 | ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % ( |
|
346 | ${(_('Comments parsed using %s syntax with %s, and %s actions support.') % ( | |
@@ -351,6 +350,23 b'' | |||||
351 | ) |
|
350 | ) | |
352 | )|n} |
|
351 | )|n} | |
353 | </div> |
|
352 | </div> | |
|
353 | ||||
|
354 | <div class="comment-attachment-text"> | |||
|
355 | <div class="dropzone-text"> | |||
|
356 | ${_("Drag'n Drop files here or")} <span class="link pick-attachment">${_('Choose your files')}</span>.<br> | |||
|
357 | </div> | |||
|
358 | <div class="dropzone-upload" style="display:none"> | |||
|
359 | <i class="icon-spin animate-spin"></i> ${_('uploading...')} | |||
|
360 | </div> | |||
|
361 | </div> | |||
|
362 | ||||
|
363 | ## comments dropzone template, empty on purpose | |||
|
364 | <div style="display: none" class="comment-attachment-uploader-template"> | |||
|
365 | <div class="dz-file-preview" style="margin: 0"> | |||
|
366 | <div class="dz-error-message"></div> | |||
|
367 | </div> | |||
|
368 | </div> | |||
|
369 | ||||
354 | </div> |
|
370 | </div> | |
355 | </div> |
|
371 | </div> | |
356 | </div> |
|
372 | </div> |
General Comments 0
You need to be logged in to leave comments.
Login now