Merge remote-tracking branch 'origin/support/3.2' into develop

This commit is contained in:
Benjamin Dalsass
2024-08-20 16:55:11 +02:00
12 changed files with 354 additions and 102 deletions

View File

@@ -2069,7 +2069,7 @@ class AttributeLinkedSet extends AttributeDefinition
public function GetImportColumns()
{
$aColumns = array();
$aColumns[$this->GetCode()] = 'TEXT'.CMDBSource::GetSqlStringColumnDefinition();
$aColumns[$this->GetCode()] = 'MEDIUMTEXT'.CMDBSource::GetSqlStringColumnDefinition();
return $aColumns;
}

View File

@@ -1,75 +1,108 @@
## Description
This is a brief description of the SASS 7-1 system and how to use it.
- [File structure](#file-structure)
- [Usage](#usage)
# Description
This is a brief description of the how the backoffice theme is structured using both BEM and SASS 7-1 systems and how to use them.
* [7-1 pattern](#7-1-pattern)
* [File structure](#file-structure)
* [Usage](#usage)
* [BEM methodology](#bem-methodology)
* [Principles](#principles)
* [Examples](#examples)
# 7-1 pattern
## File structure
SCSS files are structured following the [7-1 pattern](https://sass-guidelin.es/#the-7-1-pattern). \
@rveitch made a great summary with the following, which can also be found [here](https://gist.github.com/rveitch/84cea9650092119527bc).
_Note: Folders with an * are customizations we made to the original 7-1 pattern to best fit our needs_
_Note: Folders with an * are customizations we made to the original 7-1 pattern to best fit our needs_
```
css/backoffice/
|
| utils/
| | _variables.scss # Sass Variables
| | _functions.scss # Sass Functions
| | _mixins.scss # Sass Mixins
| | _helpers.scss # Class & placeholders helpers
| | variables/ # Sass Variables used in Functions, Mixins, Helpers, ...
| | |- colors/
| | | |- _base.scss
| | | |- _base-palette.scss # Base colors used everywhere
| | | |- _lifecycle-palette.scss # Colors used for lifecycle of an object (e.g. representing states such as new, frozen, done, ...), based on the base colors
| | | |- _semantic-palette.scss # Colors used for semantic meaning (e.g. red for errors, green for success, ...), based on the base colors
| | | ...
| | |
| | |- _depression.scss
| | |- _elevation.scss
| | |- _size.scss # Base sizes used everywhere (spacings, ...)
| | |- _spacing.scss
| | |- _typography.scss # Typography sizes, weights, families, ...
| | ...
| |
| | functions/ # Sass Functions
| | |- _color.scss # Color manipulation functions
| |
| | mixins/ # Sass Mixins
| | helpers/ # Class & placeholders helpers
|
| vendors/
| | _bootstrap.scss # Bootstrap
| | _jquery-ui.scss # jQuery UI
| ... # Etc…
| vendors/ # Third-party libs, should be either:
| # - Overload of the lib SCSS variables (BEST way, but possible only if the lib exposes them. e.g. Bulma)
| # - Overload of the lib necessary CSS classes only (not great as it duplicates some rules in the browser, which add weight and computation. e.g. dataTables)
| # - Duplicate the lib CSS completly to insert SCSS variables (not great as it will be outdated when updating the lib itself. e.g. jQuery UI)
| | _bulma-variables-overload.scss # Bulma CSS framework
| | _jquery-ui.scss # jQuery UI
| ... # Etc…
|
| base/
| | _reset.scss # Reset/normalize
| | _typography.scss # Typography rules
| ... # Etc…
| | _reset.scss # Reset/normalize
| | _typography.scss # Typography fonts imports
| ... # Etc…
|
| components/
| | _buttons.scss # Buttons
| | _carousel.scss # Carousel
| | _cover.scss # Cover
| | _dropdown.scss # Dropdown
| ... # Etc…
| components/ # Components of the UI, each corresponding to a UI block and being usable as a standalone
| | _button.scss
| | _button-group.scss
| | _global-search.scss
| | _quick-create.scss
| ...
|
| layout/
| | _navigation.scss # Navigation
| | _grid.scss # Grid system
| | _header.scss # Header
| | _footer.scss # Footer
| | _sidebar.scss # Sidebar
| | _forms.scss # Forms
| ... # Etc…
| layout/ # Elements of the UI made of several components, making the layout of the app
| | activity-panel/
| | dashboard/
| | object/ # DM object display (details, summary card, ...)
| | tab-container/
| ...
|
|- *application/ # Elements that are not usable as a standalone (like componants and layouts are) and very application (the backoffice) specific
|- *application/ # Elements that are not usable as a standalone (like componants and layouts are) and very application (the backoffice) specific
| |- display-block
| |- tabular-fields
| ...
|
|- *datamodel/ # SCSS / CSS3 variables and CSS classes for PHP classes of the DM that are part of the core (not in a module) and cannot be styled otherwise
|- *datamodel/ # SCSS / CSS3 variables and CSS classes for *PHP* classes of the DM that are part of the core (not in a module) and cannot be styled otherwise
| |- _action.scss
| |- _user.scss
| ...
|
| pages/
| | _home.scss # Home specific styles
| | _contact.scss # Contact specific styles
| ... # Etc…
| pages/ # SCSS / CSS3 variables and CSS classes for HTML elements specific to backoffice pages
| | _base.scss # Base for all backoffice pages
| | _audit.scss # Audit page
| | _csv-import.scss # CSV Import page
| ... # Etc…
|
|- *blocks-integrations # Specific rules for the integration of a block with another one, those kind of rules should never be in the block partial directly
| |- _panel-with-datatable.scss # Changes the negative margins of the datatable so it overlaps the panel's original padding
|- *blocks-integrations # Specific rules for the integration of a UI block with another one, those kind of rules should NEVER be in the block partial directly
| |- alert/
| | |- _alert-with-blocks.scss # How an alert should be displayed when after another block
| |- button/
| | |- _button-with-button.scss # How a button should be displayed when after another button
| | |- _button-with-button-group.scss # How a button should be displayed when before/after a button group
| |- panel/
| | |- _panel-with-blocks.scss # How a panel should be displayed when after another block
| | |- _panel-within-main-content.scss # How a panel becomes sticky when in the main content
| | |- _panel-within-modal.scss # How a panel becomes sticky when in a modal
| |- _tab-container-within-panel.scss # Changes the negative margins of the datatable so it overlaps the panel's original padding
| ...
|
| themes/
| | _theme.scss # Default theme
| | _admin.scss # Admin theme
| ... # Etc…
| | _page-banner.scss # ???
| ... # Etc…
|
|
`- _shame.scss # Shame file, should contain all the ugly hacks (https://sass-guidelin.es/#shame-file)
` main.scss # Main Sass file
|- _fallback.scss # Fallback file, should only contain rules that make standard HTML tags fallback to the style of a custom CSS class
|- _shame.scss # Shame file, should contain all the ugly hacks (https://sass-guidelin.es/#shame-file)
` main.scss # Main Sass file
```
## Usage
@@ -84,7 +117,58 @@ To avoid common errors, files should be imported in the final file in the follow
- Components
- Layout
- \*Application
- \*Datamodel
- Pages
- \*Block integrations
- Themes
- Shame file
- Shame file
# BEM methodology
## Principles
[BEM is a methodology](https://getbem.com/) that helps you to create reusable components and code sharing in frontend development. \
The main idea is to use discriminant classes instead of nested basic selectors for 2 main reasons:
* It's easier to understand the purpose of a specific class when seeing it in the HTML markup of the SCSS file
* It's easier to override a specific class when needed as you don't need to use a selector at least as precise/complex as the one you want to override
In our implementation, we start with the code of the UI block, followed by the sub-element, then the property or modifier. Separation is made of `--` instead of `__`.
## Examples
### Classes and CSS properties example
```scss
// SCSS variables:
// - For CSS properties: CSS class, followed by CSS property
$ibo-button--padding-y: 6px !default;
$ibo-button--padding-x: 9px !default;
$ibo-button--border: 0 !default;
$ibo-button--border-radius: $ibo-border-radius-400 !default;
$ibo-button--box-shadow-bottom: 0px 2px 0px !default;
$ibo-button--box-shadow-top: inset 0px 2px 0px !default;
$ibo-button--label--margin-left: $ibo-spacing-200 !default;
// CSS classes:
.ibo-button {
padding: $ibo-button--padding-y $ibo-button--padding-x;
border: $ibo-button--border;
border-radius: $ibo-button--border-radius;
}
.ibo-button--label {
margin-left: $ibo-button--label--margin-left;
}
```
### States example
```scss
// SCSS variables:
// Same rule as before, but with a `--is-` or `--on--` suffix
$ibo-quick-create--input--padding: $ibo-spacing-0 default;
$ibo-quick-create--input--padding-x--is-opened: $ibo-spacing-300 !default;
$ibo-quick-create--input--padding-y--is-opened: $ibo-spacing-300 !default;
$ibo-quick-create--input--width: $ibo-size-0 !default;
$ibo-quick-create--input--width--is-opened: 245px !default;
$ibo-quick-create--input--background-color: $ibo-color-white-100 !default;
$ibo-quick-create--input--background-color--on-hover: $ibo-color-grey-200 !default;
```

View File

@@ -6,5 +6,5 @@
$ibo-depression-100: inset 0 1px 1px 0 rgba(0, 0, 0, 0.15) !default;
:root{
--ibo-elevation-100: #{$ibo-depression-100};
--ibo-depression-100: #{$ibo-depression-100};
}

View File

@@ -64,6 +64,25 @@ class AttachmentPlugIn implements iApplicationUIExtension, iApplicationObjectExt
return $result;
}
/**
* @param cmdbAbstractObject $oObject
*
* @return bool
* @since 3.2.1 N°7534
*/
public static function IsAttachmentAllowedForObject(cmdbAbstractObject $oObject) : bool
{
$aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket'));
foreach ($aAllowedClasses as $sAllowedClass)
{
if ($oObject instanceof $sAllowedClass)
{
return true;
}
}
return false;
}
/**
* Returns the max. file upload size allowed as a dictionary entry
*

View File

@@ -97,6 +97,12 @@ p_object_attachment_add:
defaults:
_controller: 'Combodo\iTop\Portal\Controller\ObjectController::AttachmentAction'
p_object_attachment_display:
path: '/object/attachment/display/{sAttachmentId}'
defaults:
_controller: 'Combodo\iTop\Portal\Controller\ObjectController::AttachmentAction'
sOperation: 'display'
p_object_attachment_download:
path: '/object/attachment/download/{sAttachmentId}'
defaults:

View File

@@ -31,7 +31,7 @@ $.fn.modal.Constructor.prototype.enforceFocus = function () {
var $parent = $(e.target.parentNode);
if ($modalElement[0] !== e.target && !$modalElement.has(e.target).length &&
!$parent.hasClass('ck-input')) {
e.target.focus()
$(e.target.activeElement).focus();
}
})
};

View File

@@ -1241,6 +1241,19 @@ class ObjectController extends BrickController
break;
case 'display':
// Preparing redirection
// - Route
$aRouteParams = array(
'sObjectClass' => 'Attachment',
'sObjectId' => $this->oRequestManipulatorHelper->ReadParam('sAttachmentId', null),
'sObjectField' => 'contents',
);
$oResponse = $this->ForwardToRoute('p_object_document_display', $aRouteParams, $oRequest->query->all());
break;
default:
throw new HttpException(Response::HTTP_FORBIDDEN, Dict::S('Error:HTTP:400'));
break;

View File

@@ -94,6 +94,8 @@ class ObjectFormManager extends FormManager
*/
private $oFormHandlerHelper;
/** @var array $aPlugins plugins data */
private array $aPlugins = array();
/**
* @param string|array $formManagerData value of the formmanager_data portal parameter, either JSON or object
@@ -502,6 +504,11 @@ class ObjectFormManager extends FormManager
{
$aFieldsExtraData[$sFieldId]['opened'] = true;
}
// Checking if the field is handled by a plugin
if ($oFieldNode->hasAttribute('data-field-plugin'))
{
$aFieldsExtraData[$sFieldId]['plugin'] = $oFieldNode->getAttribute('data-field-plugin');
}
// Checking if field allows to ignore scope (For linked set)
if ($oFieldNode->hasAttribute('data-field-ignore-scopes') && ($oFieldNode->getAttribute('data-field-ignore-scopes') === 'true'))
{
@@ -651,6 +658,20 @@ class ObjectFormManager extends FormManager
// Building the form
foreach ($aFieldsAtts as $sAttCode => $iFieldFlags)
{
// handle plugins fields
if(array_key_exists($sAttCode, $aFieldsExtraData)
&& array_key_exists('plugin', $aFieldsExtraData[$sAttCode])){
$sPluginName = $aFieldsExtraData[$sAttCode]['plugin'];
switch($sPluginName){
case AttachmentPlugIn::class:
$this->AddAttachmentField($oForm, $sAttCode, $aFieldsExtraData);
break;
default:
throw new Exception('Unknown plugin ' . $sPluginName);
}
continue;
}
$oAttDef = MetaModel::GetAttributeDef(get_class($this->oObject), $sAttCode);
/** @var Field $oField */
@@ -990,49 +1011,12 @@ class ObjectFormManager extends FormManager
}
}
// Checking if the instance has attachments
if (class_exists('Attachment') && class_exists('AttachmentPlugIn'))
{
// Checking if the object is allowed for attachments
$bClassAllowed = false;
$aAllowedClasses = MetaModel::GetModuleSetting('itop-attachments', 'allowed_classes', array('Ticket'));
foreach ($aAllowedClasses as $sAllowedClass)
{
if ($this->oObject instanceof $sAllowedClass)
{
$bClassAllowed = true;
break;
}
}
// Adding attachment field
if ($bClassAllowed)
{
// set id to a unique key - avoid collisions with another attribute that could exist with the name 'attachments'
$oField = new FileUploadField('attachments_plugin');
$oField->SetLabel(Dict::S('Portal:Attachments'))
->SetUploadEndpoint($this->oFormHandlerHelper->GetUrlGenerator()->generate('p_object_attachment_add'))
->SetDownloadEndpoint($this->oFormHandlerHelper->GetUrlGenerator()->generate('p_object_attachment_download',
array('sAttachmentId' => '-sAttachmentId-')))
->SetTransactionId($oForm->GetTransactionId())
->SetAllowDelete($this->oFormHandlerHelper->getCombodoPortalConf()['properties']['attachments']['allow_delete'])
->SetObject($this->oObject);
// Checking if we can edit attachments in the current state
if (($this->sMode === static::ENUM_MODE_VIEW)
|| AttachmentPlugIn::IsReadonlyState($this->oObject, $this->oObject->GetState(),
AttachmentPlugIn::ENUM_GUI_PORTALS) === true
|| $oForm->GetEditableFieldCount(true) === 0)
{
$oField->SetReadOnly(true);
}
// Adding attachements field in transition only if it is editable
if (!$this->IsTransitionForm() || ($this->IsTransitionForm() && !$oField->GetReadOnly()))
{
$oForm->AddField($oField);
}
}
// fallback Checking if the instance has attachments
// (in case attachment is not explicitly declared in layout)
if (class_exists('Attachment') && class_exists('AttachmentPlugIn')
&& !$this->IsPluginInitialized(AttachmentPlugIn::class)
&& AttachmentPlugIn::IsAttachmentAllowedForObject($this->oObject)){
$this->AddAttachmentField($oForm, 'attachments_plugin', $aFieldsExtraData);
}
$oForm->Finalize();
@@ -1040,6 +1024,78 @@ class ObjectFormManager extends FormManager
$this->oRenderer->SetForm($this->oForm);
}
/**
* IsPluginInitialized.
*
* @param string $sPluginName
*
* @return bool
*/
private function IsPluginInitialized(string $sPluginName) : bool
{
return array_key_exists($sPluginName, $this->aPlugins);
}
/**
* AddAttachmentField.
*
* @param $oForm
* @param $sId
* @param $aFieldsExtraData
*
* @throws \Exception
*/
private function AddAttachmentField($oForm, $sId, $aFieldsExtraData) : void
{
// only one instance allowed
if($this->IsPluginInitialized(AttachmentPlugIn::class)){
throw new Exception("Unable to process field `$sId`, AttachmentPlugIn has already been initialized with field `" . $this->aPlugins[AttachmentPlugIn::class]['field']->GetId() . '`');
}
// not allowed for object class
if(!AttachmentPlugIn::IsAttachmentAllowedForObject($this->oObject)){
throw new Exception("Unable to process field `$sId`, AttachmentPlugIn is not allowed for class `" . $this->oObject::class . '`');
}
// set id to a unique key - avoid collisions with another attribute that could exist with the name 'attachments'
$oField = new FileUploadField($sId);
$oField->SetLabel(Dict::S('Portal:Attachments'))
->SetUploadEndpoint($this->oFormHandlerHelper->GetUrlGenerator()->generate('p_object_attachment_add'))
->SetDownloadEndpoint($this->oFormHandlerHelper->GetUrlGenerator()->generate('p_object_attachment_download',
array('sAttachmentId' => '-sAttachmentId-')))
->SetDisplayEndpoint($this->oFormHandlerHelper->GetUrlGenerator()->generate('p_object_attachment_display',
array('sAttachmentId' => '-sAttachmentId-')))
->SetTransactionId($oForm->GetTransactionId())
->SetAllowDelete($this->oFormHandlerHelper->getCombodoPortalConf()['properties']['attachments']['allow_delete'])
->SetObject($this->oObject);
// Checking if we can edit attachments in the current state
$oObjectFormManager = $this;
$oField->SetOnFinalizeCallback(function() use ($oObjectFormManager, $oForm, $oField){
if (($oObjectFormManager->sMode === static::ENUM_MODE_VIEW)
|| AttachmentPlugIn::IsReadonlyState($oObjectFormManager->oObject, $oObjectFormManager->oObject->GetState(),
AttachmentPlugIn::ENUM_GUI_PORTALS) === true
|| $oForm->GetEditableFieldCount(true) === 0)
{
$oField->SetReadOnly(true);
}
});
if (array_key_exists($sId, $aFieldsExtraData) && array_key_exists('opened', $aFieldsExtraData[$sId])){
$oField->SetDisplayOpened(true);
}
// Adding attachments field in transition only if it is editable
if (!$this->IsTransitionForm() || !$oField->GetReadOnly()){
$oForm->AddField($oField);
}
// save plugin data
$this->aPlugins[AttachmentPlugIn::class] = [
'field' => $oField,
];
}
/**
* @inheritDoc
*

View File

@@ -2259,7 +2259,7 @@ EOF
$this->CompileCommonProperty('is_null_allowed', $oField, $aParameters, $sModuleRelativeDir, false);
$this->CompileCommonProperty('default_value', $oField, $aParameters, $sModuleRelativeDir, '');
$this->CompileCommonProperty('allowed_values', $oField, $aParameters, $sModuleRelativeDir);
$aParameters['class_category'] = $this->GetPropString($oField, 'class_category');
$aParameters['class_category'] = $this->GetPropString($oField, 'class_category', '');
$aParameters['more_values'] = $this->GetPropString($oField, 'more_values', '');
$aParameters['depends_on'] = $sDependencies;
}else {

View File

@@ -30,6 +30,11 @@ class FileUploadField extends AbstractSimpleField
{
/** @var bool DEFAULT_ALLOW_DELETE */
const DEFAULT_ALLOW_DELETE = true;
/**
* @var bool DEFAULT_DISPLAY_OPENED
* @since 3.2.1 N°7534
*/
const DEFAULT_DISPLAY_OPENED = false;
/** @var string|null $sTransactionId */
protected $sTransactionId;
@@ -39,8 +44,18 @@ class FileUploadField extends AbstractSimpleField
protected $sUploadEndpoint;
/** @var string|null $sDownloadEndpoint */
protected $sDownloadEndpoint;
/**
* @var string|null $sViewEndpoint
* @since 3.2.1 N°7534
*/
protected ?string $sDisplayEndpoint;
/** @var bool $bAllowDelete */
protected $bAllowDelete;
/**
* @var bool $bDisplayOpened
* @since 3.2.1 N°7534
*/
protected bool $bDisplayOpened;
/**
* @inheritDoc
@@ -51,7 +66,9 @@ class FileUploadField extends AbstractSimpleField
$this->oObject = null;
$this->sUploadEndpoint = null;
$this->sDownloadEndpoint = null;
$this->sDisplayEndpoint = null;
$this->bAllowDelete = static::DEFAULT_ALLOW_DELETE;
$this->bDisplayOpened = static::DEFAULT_DISPLAY_OPENED;
parent::__construct($sId, $onFinalizeCallback);
}
@@ -134,6 +151,27 @@ class FileUploadField extends AbstractSimpleField
return $this;
}
/**
* @return string|null
* @since 3.2.1 N°7534
*/
public function GetDisplayEndpoint(): ?string
{
return $this->sDisplayEndpoint;
}
/**
* @param string $sDisplayEndpoint
*
* @return FileUploadField
* @since 3.2.1 N°7534
*/
public function SetDisplayEndpoint(string $sDisplayEndpoint): FileUploadField
{
$this->sDisplayEndpoint = $sDisplayEndpoint;
return $this;
}
/**
* @return bool
*/
@@ -153,4 +191,29 @@ class FileUploadField extends AbstractSimpleField
return $this;
}
/**
* Sets if the field should be displayed opened on initialization
*
* @param bool $bDisplayOpened
*
* @return FileUploadField
* @since 3.2.1 N°7534
*/
public function SetDisplayOpened(bool $bDisplayOpened) : FileUploadField
{
$this->bDisplayOpened = $bDisplayOpened;
return $this;
}
/**
* Returns if the field should be displayed opened on initialization
*
* @return boolean
* @since 3.2.1 N°7534
*/
public function GetDisplayOpened() : bool
{
return $this->bDisplayOpened;
}
}

View File

@@ -80,11 +80,17 @@ class BsFileUploadFieldRenderer extends BsFieldRenderer
$sFieldWrapperId = 'form_upload_wrapper_' . $this->oField->GetGlobalId();
$sFieldDescriptionForHTMLTag = ($this->oField->HasDescription()) ? 'data-tooltip-content="'.utils::HtmlEntities($this->oField->GetDescription()).'"' : '';
// If collapsed
$sCollapseTogglerClass .= ' collapsed';
$sCollapseTogglerExpanded = 'false';
$sCollapseTogglerIconClass = $sCollapseTogglerIconHiddenClass;
$sCollapseJSInitState = 'false';
// Preparing collapsed state
if ($this->oField->GetDisplayOpened()) {
$sCollapseTogglerExpanded = 'true';
$sCollapseTogglerIconClass = $sCollapseTogglerIconVisibleClass;
$sCollapseJSInitState = 'true';
} else {
$sCollapseTogglerClass .= ' collapsed';
$sCollapseTogglerExpanded = 'false';
$sCollapseTogglerIconClass = $sCollapseTogglerIconHiddenClass;
$sCollapseJSInitState = 'false';
}
// Label
$oOutput->AddHtml('<div class="form_field_label">');
@@ -183,6 +189,7 @@ JS
'{{iAttId}}',
'{{sLineStyle}}',
'{{sDocDownloadUrl}}',
'{{sDocDisplayUrl}}',
true,
'{{sAttachmentThumbUrl}}',
'{{sFileName}}',
@@ -234,6 +241,7 @@ JS
var \$oAttachmentTBody = $(this).closest('.fileupload_field_content').find('.attachments_container table#$sAttachmentTableId>tbody'),
iAttId = data.result.att_id,
sDownloadLink = '{$this->oField->GetDownloadEndpoint()}'.replace(/-sAttachmentId-/, iAttId),
sDisplayLink = '{$this->oField->GetDisplayEndpoint()}'.replace(/-sAttachmentId-/, iAttId),
sAttachmentMeta = '<input id="attachment_'+iAttId+'" type="hidden" name="attachments[]" value="'+iAttId+'"/>';
// hide "no attachment" line if present
@@ -247,6 +255,7 @@ JS
{search: "{{iAttId}}", replace:iAttId },
{search: "{{lineStyle}}", replace:'' },
{search: "{{sDocDownloadUrl}}", replace:sDownloadLink },
{search: "{{sDocDisplayUrl}}", replace:sDisplayLink },
{search: "{{sAttachmentThumbUrl}}", replace:data.result.icon },
{search: "{{sFileName}}", replace: data.result.msg },
{search: "{{sAttachmentMeta}}", replace:sAttachmentMeta },
@@ -402,6 +411,7 @@ HTML
$sFileName = utils::EscapeHtml($oDoc->GetFileName());
$sDocDownloadUrl = str_replace('-sAttachmentId-', $iAttId, $this->oField->GetDownloadEndpoint());
$sDocDisplayUrl = str_replace('-sAttachmentId-', $iAttId, $this->oField->GetDisplayEndpoint());
$sAttachmentThumbUrl = utils::GetAbsoluteUrlAppRoot().AttachmentPlugIn::GetFileIcon($sFileName);
$bHasPreview = false;
@@ -431,6 +441,7 @@ HTML
$iAttId,
$sLineStyle,
$sDocDownloadUrl,
$sDocDisplayUrl,
$bHasPreview,
$sAttachmentThumbUrl,
$sFileName,
@@ -485,6 +496,7 @@ HTML;
* @param int $iAttId
* @param string $sLineStyle
* @param string $sDocDownloadUrl
* @param string $sDocDisplayUrl
* @param bool $bHasPreview replace string $sIconClass since 3.0.1
* @param string $sAttachmentThumbUrl
* @param string $sFileName
@@ -499,7 +511,7 @@ HTML;
* @since 2.7.0
*/
protected static function GetAttachmentTableRow(
$iAttId, $sLineStyle, $sDocDownloadUrl, $bHasPreview, $sAttachmentThumbUrl, $sFileName, $sAttachmentMeta, $sFileSize,
$iAttId, $sLineStyle, $sDocDownloadUrl, $sDocDisplayUrl, $bHasPreview, $sAttachmentThumbUrl, $sFileName, $sAttachmentMeta, $sFileSize,
$iFileSizeRaw, $iFileDownloadsCount, $sAttachmentDate, $iAttachmentDateRaw, $bIsDeleteAllowed
) {
$sDeleteCell = '';
@@ -511,16 +523,16 @@ HTML;
$sHtml = "<tr id=\"display_attachment_{$iAttId}\" class=\"attachment\" $sLineStyle>";
if($bHasPreview) {
$sHtml .= "<td role=\"icon\"><a href=\"$sDocDownloadUrl\" target=\"_blank\" data-tooltip-content=\"<img class='attachment-tooltip' src='{$sDocDownloadUrl}'>\" data-tooltip-html-enabled=true><img src=\"$sAttachmentThumbUrl\" ></a></td>";
$sHtml .= "<td role=\"icon\"><a href=\"$sDocDisplayUrl\" target=\"_blank\" data-tooltip-content=\"<img class='attachment-tooltip' src='{$sDocDownloadUrl}'>\" data-tooltip-html-enabled=true><img src=\"$sAttachmentThumbUrl\" ></a></td>";
} else {
$sHtml .= "<td role=\"icon\"><a href=\"$sDocDownloadUrl\" target=\"_blank\"><img src=\"$sAttachmentThumbUrl\" ></a></td>";
$sHtml .= "<td role=\"icon\"><a href=\"$sDocDisplayUrl\" target=\"_blank\"><img src=\"$sAttachmentThumbUrl\" ></a></td>";
}
$sHtml .= <<<HTML
<td role="filename"><a href="$sDocDownloadUrl" target="_blank">$sFileName</a>$sAttachmentMeta</td>
<td role="filename"><a href="$sDocDisplayUrl" target="_blank">$sFileName</a>$sAttachmentMeta</td>
<td role="formatted-size" data-order="$iFileSizeRaw">$sFileSize</td>
<td role="upload-date" data-order="$iAttachmentDateRaw">$sAttachmentDate</td>
<td role="downloads-count">$iFileDownloadsCount</td>
<td role="downloads-count"><a href="$sDocDownloadUrl" target="_blank"><span class="fas fa-download fa-lg" style="float: right;"></span></a>$iFileDownloadsCount</td>
$sDeleteCell
</tr>
HTML;

View File

@@ -254,7 +254,6 @@ JS
CombodoCKEditorHandler.GetInstance("#{$this->oField->GetGlobalId()}")
.then((oCKEditor) => {
oCKEditor.model.document.on("change:data", () => {
console.log("desc changed!");
const oFieldElem = $("#{$this->oField->GetGlobalId()}");
oFieldElem.closest(".field_set").trigger("field_change", {
id: oFieldElem.attr("id"),