Merge remote-tracking branch 'origin/develop'

# Conflicts:
#	public/css/build/app.css
#	public/css/build/app.css.map
#	public/css/build/overrides.css
#	public/css/build/overrides.css.map
#	public/css/dist/all.css
#	public/mix-manifest.json
This commit is contained in:
snipe
2026-05-30 10:05:28 +01:00
22 changed files with 245 additions and 102 deletions
+38
View File
@@ -1856,4 +1856,42 @@ class Helper
return 'App\\Models\\'.ucwords($model);
}
/**
* Render a markdown-textarea value as HTML.
*
* Soft line breaks (single newlines) are rendered as <br> so that line
* breaks typed in the textarea are preserved in the output.
*
* When $inline is true, block-level elements are suppressed and hard
* breaks are pre-processed manually — used for the encrypted reveal span
* where block HTML cannot be placed inside a font-size-toggled <span>.
*/
public static function renderMarkdown(?string $text, bool $inline = false): string
{
if (empty($text)) {
return '';
}
if ($inline) {
// Convert newlines to CommonMark hard breaks for inline rendering
$text = preg_replace('/(?<! {2})\n/', " \n", $text);
return Str::inlineMarkdown($text, ['html_input' => 'escape']);
}
$html = trim(Str::markdown($text, [
'html_input' => 'escape',
'renderer' => ['soft_break' => "<br>\n"],
]));
// If the entire output is a single <p> block, unwrap it so the content
// renders inline-ish without the <p> adding unwanted top spacing in the
// compact detail-view layout.
if (str_starts_with($html, '<p>') && str_ends_with($html, '</p>') && substr_count($html, '<p>') === 1) {
return substr($html, 3, -4);
}
return $html;
}
}
@@ -53,6 +53,8 @@ class CheckoutKitController extends Controller
*/
public function store(Request $request, $kit_id)
{
$this->authorize('checkout', Asset::class);
$user_id = e($request->input('user_id'));
if (is_null($user = User::find($user_id))) {
return redirect()->back()->with('error', trans('admin/users/message.user_not_found'));
@@ -222,6 +222,10 @@ class ViewAssetsController extends Controller
return redirect()->back()->with('success')->with('success', trans('admin/hardware/message.requests.canceled'));
} else {
if ($fullItemType === Asset::class && is_null(Asset::RequestableAssets()->find($item->id))) {
return redirect()->back()->with('error', trans('admin/hardware/message.requests.error'));
}
$item->request();
if (($settings->alert_email != '') && ($settings->alerts_enabled == '1') && (! config('app.lock_passwords'))) {
$logaction->logaction('requested');
+3 -3
View File
@@ -144,7 +144,7 @@ class AssetsTransformer
$fields_array[$field->name] = [
'field' => e($field->db_column),
'value' => e($value),
'value' => ($field->element == 'markdown-textarea' && Gate::allows('assets.view.encrypted_custom_fields')) ? Helper::renderMarkdown($value) : e($value),
'field_format' => $field->format,
'element' => $field->element,
];
@@ -158,7 +158,7 @@ class AssetsTransformer
$fields_array[$field->name] = [
'field' => e($field->db_column),
'value' => e($value),
'value' => ($field->element == 'markdown-textarea') ? Helper::renderMarkdown($value) : e($value),
'field_format' => $field->format,
'element' => $field->element,
];
@@ -274,7 +274,7 @@ class AssetsTransformer
$value = Helper::getFormattedDateObject($value, 'date', false);
}
$fields_array[$field->db_column] = e($value);
$fields_array[$field->db_column] = ($field->element == 'markdown-textarea') ? Helper::renderMarkdown($value) : e($value);
}
$array['custom_fields'] = $fields_array;
+1 -1
View File
@@ -51,7 +51,7 @@ class CustomField extends Model
*/
protected $rules = [
'name' => 'required|unique:custom_fields',
'element' => 'required|in:text,listbox,textarea,checkbox,radio',
'element' => 'required|in:text,listbox,textarea,markdown-textarea,checkbox,radio',
'field_encrypted' => 'nullable|boolean',
'auto_add_to_fieldsets' => 'boolean',
'show_in_listview' => 'boolean',
+12 -9
View File
@@ -7,11 +7,11 @@ use App\Models\PredefinedKit;
use App\Models\User;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\DB;
/**
* Class incapsulates checkout logic for reuse in different controllers
*
* @author [D. Minaev.] [<dmitriy.minaev.v@gmail.com>]
*/
class PredefinedKitCheckoutService
@@ -19,9 +19,9 @@ class PredefinedKitCheckoutService
use AuthorizesRequests;
/**
* @param Request $request, this function works with fields: checkout_at, expected_checkin, note
* @param PredefinedKit $kit kit for checkout
* @param User $user checkout target
* @param Request $request, this function works with fields: checkout_at, expected_checkin, note
* @param PredefinedKit $kit kit for checkout
* @param User $user checkout target
* @return array Empty array if all ok, else [string_error1, string_error2...]
*/
public function checkout(Request $request, PredefinedKit $kit, User $user)
@@ -93,7 +93,7 @@ class PredefinedKitCheckoutService
}
}
if ($quantity > 0) {
$errors[] = trans('admin/kits/general.none_models', ['model'=> $model->name, 'qty' => $model->pivot->quantity]);
$errors[] = trans('admin/kits/general.none_models', ['model' => $model->name, 'qty' => $model->pivot->quantity]);
}
}
@@ -107,9 +107,10 @@ class PredefinedKitCheckoutService
->with('freeSeats')
->get();
foreach ($licenses as $license) {
$this->authorize('checkout', $license);
$quantity = $license->pivot->quantity;
if ($quantity > count($license->freeSeats)) {
$errors[] = trans('admin/kits/general.none_licenses', ['license'=> $license->name, 'qty' => $license->pivot->quantity]);
$errors[] = trans('admin/kits/general.none_licenses', ['license' => $license->name, 'qty' => $license->pivot->quantity]);
}
for ($i = 0; $i < $quantity; $i++) {
$seats_to_add[] = $license->freeSeats[$i];
@@ -123,8 +124,9 @@ class PredefinedKitCheckoutService
{
$consumables = $kit->consumables()->with('users')->get();
foreach ($consumables as $consumable) {
$this->authorize('checkout', $consumable);
if ($consumable->numRemaining() < $consumable->pivot->quantity) {
$errors[] = trans('admin/kits/general.none_consumables', ['consumable'=> $consumable->name, 'qty' => $consumable->pivot->quantity]);
$errors[] = trans('admin/kits/general.none_consumables', ['consumable' => $consumable->name, 'qty' => $consumable->pivot->quantity]);
}
}
@@ -135,8 +137,9 @@ class PredefinedKitCheckoutService
{
$accessories = $kit->accessories()->with('users')->get();
foreach ($accessories as $accessory) {
$this->authorize('checkout', $accessory);
if ($accessory->numRemaining() < $accessory->pivot->quantity) {
$errors[] = trans('admin/kits/general.none_accessory', ['accessory'=> $accessory->name, 'qty' => $accessory->pivot->quantity]);
$errors[] = trans('admin/kits/general.none_accessory', ['accessory' => $accessory->name, 'qty' => $accessory->pivot->quantity]);
}
}
@@ -175,7 +178,7 @@ class PredefinedKitCheckoutService
]);
event(new CheckoutableCheckedOut($consumable, $user, $admin, $note));
}
//accessories
// accessories
foreach ($accessories_to_add as $accessory) {
$accessory->assigned_to = $user->id;
$accessory->users()->attach($accessory->id, [
+11
View File
@@ -154,4 +154,15 @@ class CustomFieldFactory extends Factory
];
});
}
public function testMarkdownTextarea()
{
return $this->state(function () {
return [
'name' => 'Notes',
'help_text' => 'Additional notes about this asset. Markdown is supported.',
'element' => 'markdown-textarea',
];
});
}
}
+15
View File
@@ -36,6 +36,7 @@ class CustomFieldSeeder extends Seeder
CustomField::factory()->count(1)->testEncrypted()->create();
CustomField::factory()->count(1)->testCheckbox()->create();
CustomField::factory()->count(1)->testRadio()->create();
CustomField::factory()->count(1)->testMarkdownTextarea()->create();
DB::table('custom_field_custom_fieldset')->insert([
[
@@ -109,6 +110,20 @@ class CustomFieldSeeder extends Seeder
'required' => 0,
],
[
'custom_field_id' => '9',
'custom_fieldset_id' => '1',
'order' => 0,
'required' => 0,
],
[
'custom_field_id' => '9',
'custom_fieldset_id' => '2',
'order' => 0,
'required' => 0,
],
]);
}
}
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+1 -1
View File
File diff suppressed because one or more lines are too long
+3 -3
View File
@@ -1,9 +1,9 @@
{
"/js/dist/all.js": "/js/dist/all.js?id=571ba00068523222c399e1ec11b89288",
"/css/build/overrides.css": "/css/build/overrides.css?id=9bfab28a94932d45568ad50f3c6c5e2c",
"/css/build/app.css": "/css/build/app.css?id=4b2abd7fa3560ada549e9d08bd836aa8",
"/css/build/overrides.css": "/css/build/overrides.css?id=9f70623c0b56ef8ecec6fb859344d279",
"/css/build/app.css": "/css/build/app.css?id=3cfdd1824004111aea48a50793be2518",
"/css/build/AdminLTE.css": "/css/build/AdminLTE.css?id=bdf169bc2141f453390614c138cdce95",
"/css/dist/all.css": "/css/dist/all.css?id=f5f404325dedd1abd00dc781664c0034",
"/css/dist/all.css": "/css/dist/all.css?id=144c0b82011f8e02e3f71ba389755549",
"/css/dist/signature-pad.css": "/css/dist/signature-pad.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/css/dist/signature-pad.min.css": "/css/dist/signature-pad.min.css?id=6a89d3cd901305e66ced1cf5f13147f7",
"/js/select2/i18n/af.js": "/js/select2/i18n/af.js?id=4f6fcd73488ce79fae1b7a90aceaecde",
+67 -47
View File
@@ -340,6 +340,12 @@ body {
z-index: 0 !important;
}
// Clip the absolutely-positioned fixed-column overlays so they cannot
// bleed past the container's bottom edge into the pagination area.
.bootstrap-table .fixed-table-container {
overflow: hidden;
}
@media print {
@page {
@@ -695,21 +701,21 @@ h4 {
See https://github.com/grokability/snipe-it/issues/7989
*/
th.css-accessory > .th-inner,
th.css-accessory-alt > .th-inner,
th.css-barcode > .th-inner,
th.css-component > .th-inner,
th.css-consumable > .th-inner,
th.css-envelope > .th-inner,
th.css-house-flag > .th-inner,
th.css-house-laptop > .th-inner,
th.css-house-user > .th-inner,
th.css-license > .th-inner,
th.css-location > .th-inner,
th.css-users > .th-inner,
th.css-currency > .th-inner,
th.css-child-locations > .th-inner,
th.css-history > .th-inner
thead th.css-accessory > .th-inner,
thead th.css-accessory-alt > .th-inner,
thead th.css-barcode > .th-inner,
thead th.css-component > .th-inner,
thead th.css-consumable > .th-inner,
thead th.css-envelope > .th-inner,
thead th.css-house-flag > .th-inner,
thead th.css-house-laptop > .th-inner,
thead th.css-house-user > .th-inner,
thead th.css-license > .th-inner,
thead th.css-location > .th-inner,
thead th.css-users > .th-inner,
thead th.css-currency > .th-inner,
thead th.css-child-locations > .th-inner,
thead th.css-history > .th-inner
{
font-size: 0px;
line-height: 0.75 !important;
@@ -720,22 +726,22 @@ th.css-history > .th-inner
}
th.css-location > .th-inner::before,
th.css-accessory > .th-inner::before,
th.css-accessory-alt > .th-inner::before,
th.css-barcode > .th-inner::before,
th.css-component > .th-inner::before,
th.css-consumable > .th-inner::before,
th.css-envelope > .th-inner::before,
th.css-house-flag > .th-inner::before,
th.css-house-laptop > .th-inner::before,
th.css-house-user > .th-inner::before,
th.css-license > .th-inner::before,
th.css-location > .th-inner::before,
th.css-users > .th-inner::before,
th.css-currency > .th-inner::before,
th.css-child-locations > .th-inner::before,
th.css-history > .th-inner::before
thead th.css-location > .th-inner::before,
thead th.css-accessory > .th-inner::before,
thead th.css-accessory-alt > .th-inner::before,
thead th.css-barcode > .th-inner::before,
thead th.css-component > .th-inner::before,
thead th.css-consumable > .th-inner::before,
thead th.css-envelope > .th-inner::before,
thead th.css-house-flag > .th-inner::before,
thead th.css-house-laptop > .th-inner::before,
thead th.css-house-user > .th-inner::before,
thead th.css-license > .th-inner::before,
thead th.css-location > .th-inner::before,
thead th.css-users > .th-inner::before,
thead th.css-currency > .th-inner::before,
thead th.css-child-locations > .th-inner::before,
thead th.css-history > .th-inner::before
{
display: inline-block;
font-size: 20px;
@@ -747,91 +753,91 @@ th.css-history > .th-inner::before
BEGIN ICON TABLE HEADERS
Set the font-weight css property as 900 (For Solid), 400 (Regular or Brands), 300 (Light for pro icons).
**/
th.css-barcode > .th-inner::before
thead th.css-barcode > .th-inner::before
{
content: "\f02a"; font-family: "Font Awesome 5 Free"; font-weight: 900;
}
th.css-license > .th-inner::before
thead th.css-license > .th-inner::before
{
content: "\f0c7"; font-family: "Font Awesome 5 Free"; font-weight: 400;
}
th.css-consumable > .th-inner::before
thead th.css-consumable > .th-inner::before
{
content: "\f043"; font-family: "Font Awesome 5 Free"; font-weight: 900;
}
th.css-envelope > .th-inner::before
thead th.css-envelope > .th-inner::before
{
content: "\f0e0"; font-family: "Font Awesome 5 Free"; font-weight: 400;
}
th.css-accessory > .th-inner::before
thead th.css-accessory > .th-inner::before
{
content: "\f11c"; font-family: "Font Awesome 5 Free"; font-weight: 400;
}
th.css-users > .th-inner::before {
thead th.css-users > .th-inner::before {
content: "\f0c0"; font-family: "Font Awesome 5 Free"; font-size: 15px;
}
th.css-location > .th-inner::before {
thead th.css-location > .th-inner::before {
content: "\f3c5"; font-family: "Font Awesome 5 Free"; font-size: 19px; margin-bottom: 0px;
}
th.css-component > .th-inner::before
thead th.css-component > .th-inner::before
{
content: "\f0a0"; font-family: "Font Awesome 5 Free"; font-weight: 500;
}
th.css-padlock > .th-inner::before
thead th.css-padlock > .th-inner::before
{
content: "\f023"; font-family: "Font Awesome 5 Free";
font-weight: 800;
padding-right: 3px;
}
th.css-house-user > .th-inner::before {
thead th.css-house-user > .th-inner::before {
content: "\e1b0";
font-family: "Font Awesome 5 Free";
font-size: 19px;
margin-bottom: 0px;
}
th.css-house-flag > .th-inner::before {
thead th.css-house-flag > .th-inner::before {
content: "\e50d";
font-family: "Font Awesome 5 Free";
font-size: 19px;
margin-bottom: 0px;
}
th.css-house-laptop > .th-inner::before {
thead th.css-house-laptop > .th-inner::before {
content: "\e066";
font-family: "Font Awesome 5 Free";
font-size: 19px;
margin-bottom: 0px;
}
th.css-accessory-alt > .th-inner::before {
thead th.css-accessory-alt > .th-inner::before {
content: "\f11c";
font-family: "Font Awesome 5 Free";
font-size: 19px;
margin-bottom: 0px;
}
th.css-child-locations > .th-inner::before {
thead th.css-child-locations > .th-inner::before {
content: "\f64f"; // change this to f51e for coins
font-family: "Font Awesome 5 Free";
font-size: 19px;
margin-bottom: 0px;
}
th.css-currency > .th-inner::before {
thead th.css-currency > .th-inner::before {
content: "\24"; // change this to f51e for coins
font-family: "Font Awesome 5 Free";
font-size: 19px;
margin-bottom: 0px;
}
th.css-history > .th-inner::before {
thead th.css-history > .th-inner::before {
content: "\f1da"; // change this to f51e for coins
font-family: "Font Awesome 5 Free";
font-size: 19px;
@@ -1186,6 +1192,19 @@ caption.tableCaption {
padding-left: 8px;
}
.markdown-field-content {
h1 { font-size: 22px; font-weight: 700; margin: 6px 0 4px; }
h2 { font-size: 20px; font-weight: 700; margin: 6px 0 4px; }
h3 { font-size: 18px; font-weight: 700; margin: 6px 0 4px; }
h4 { font-size: 16px; font-weight: 700; margin: 4px 0 4px; }
h5, h6 { font-size: 14px; font-weight: 700; margin: 4px 0 4px; }
p { margin: 0 0 4px; }
p:last-child { margin-bottom: 0; }
ul, ol { margin: 0 0 4px; padding-left: 20px; }
> *:first-child { margin-top: 0; }
}
// via https://github.com/grokability/snipe-it/issues/11754
.sidebar-toggle.btn {
border-radius: 3px;
@@ -1307,6 +1326,7 @@ Radio toggle styles for permission settings and check/uncheck all
}
.js-copy-link {
float: left;
color: grey;
}
@@ -64,8 +64,10 @@ return [
'text' => 'Text Box',
'listbox' => 'List Box',
'textarea' => 'Textarea (multi-line)',
'markdown-textarea' => 'Markdown Textarea',
'checkbox' => 'Checkbox',
'radio' => 'Radio Buttons',
],
'markdown_supported' => 'Markdown is supported',
'general_help_text' => 'Custom fields store additional information not covered by the default asset fields. <a href="https://snipe-it.readme.io/docs/custom-fields#/"><i class="fa fa-external-link"></i></a>.',
];
@@ -8,42 +8,48 @@
</x-copy-to-clipboard>
{{-- Hidden span used as copy target --}}
{{-- It's tempting to break out the HTML into separate lines for this, but it results in extra spaces being added onto the end of the copied value --}}
{{-- For markdown fields, position: absolute removes it from normal flow so it can't create an anonymous block box --}}
@if (($field->field_encrypted=='1') && (Gate::allows('assets.view.encrypted_custom_fields')))
<span class="js-copy-{{ $field->id }} visually-hidden hidden-print" style="font-size: 0px;">{{ ($field->isFieldDecryptable($item->{$field->db_column_name()}) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $item->{$field->db_column_name()}) }}</span>
<span class="js-copy-{{ $field->id }} visually-hidden hidden-print" style="font-size: 0;{{ $field->element === 'markdown-textarea' ? ' position: absolute;' : '' }}">{{ ($field->isFieldDecryptable($item->{$field->db_column_name()}) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $item->{$field->db_column_name()}) }}</span>
@elseif (($field->field_encrypted=='1') && (Gate::denies('assets.view.encrypted_custom_fields')))
<span class="js-copy-{{ $field->id }} visually-hidden hidden-print" style="font-size: 0px;">{{ strtoupper(trans('admin/custom_fields/general.encrypted')) }}</span>
<span class="js-copy-{{ $field->id }} visually-hidden hidden-print" style="font-size: 0;{{ $field->element === 'markdown-textarea' ? ' position: absolute;' : '' }}">{{ strtoupper(trans('admin/custom_fields/general.encrypted')) }}</span>
@else
<span class="js-copy-{{ $field->id }} visually-hidden hidden-print" style="font-size: 0px;">{{ $item->{$field->db_column_name()} }}</span>
<span class="js-copy-{{ $field->id }} visually-hidden hidden-print" style="font-size: 0;{{ $field->element === 'markdown-textarea' ? ' position: absolute;' : '' }}">{{ $item->{$field->db_column_name()} }}</span>
@endif
@endif
@if (($field->field_encrypted=='1') && ($item->{$field->db_column_name()}!='') && (Gate::allows('assets.view.encrypted_custom_fields')))
<i class="fas fa-lock" data-tooltip="true" data-placement="top" title="{{ trans('admin/custom_fields/general.value_encrypted') }}" onclick="showHideEncValue(this)" id="text-{{ $field->id }}"></i>
<i class="fas fa-lock fa-fw pull-right" style="font-size: 16px;" data-tooltip="true" data-placement="top" title="{{ trans('admin/custom_fields/general.value_encrypted') }}" onclick="showHideEncValue(this)" id="text-{{ $field->id }}"></i>
@endif
@if ($field->isFieldDecryptable($item->{$field->db_column_name()} ))
@can('assets.view.encrypted_custom_fields')
@php
$fieldSize = strlen(Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}))
$fieldSize = strlen(\App\Helpers\Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}))
@endphp
@if ($fieldSize > 0)
<span id="text-{{ $field->id }}-to-hide">***********</span>
<span id="text-{{ $field->id }}-to-hide" style="font-size: 20px;vertical-align:middle;">***********</span>
@if (($field->format=='URL') && ($item->{$field->db_column_name()}!=''))
<span class="js-copy-{{ $field->id }} hidden-print"
id="text-{{ $field->id }}-to-show"
style="font-size: 0px;">
<a href="{{ Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) }}"
target="_new">{{ Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) }}</a>
</span>
style="font-size: 0;">
<a href="{{ Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) }}"
target="_new">{{ Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) }}</a>
</span>
@elseif (($field->format=='DATE') && ($item->{$field->db_column_name()}!=''))
<span class="js-copy-{{ $field->id }} hidden-print"
id="text-{{ $field->id }}-to-show"
style="font-size: 0px;">{{ \App\Helpers\Helper::gracefulDecrypt($field, \App\Helpers\Helper::getFormattedDateObject($item->{$field->db_column_name()}, 'date', false)) }}</span>
style="font-size: 0;">{{ \App\Helpers\Helper::gracefulDecrypt($field, \App\Helpers\Helper::getFormattedDateObject($item->{$field->db_column_name()}, 'date', false)) }}</span>
@elseif ($field->element == 'markdown-textarea')
<div class="js-copy-{{ $field->id }} hidden-print markdown-field-content"
id="text-{{ $field->id }}-to-show"
data-markdown="true"
style="display: none;">{!! Helper::renderMarkdown(Helper::gracefulDecrypt($field, $item->{$field->db_column_name()})) !!}</div>
@else
<span class="js-copy-{{ $field->id }} hidden-print"
id="text-{{ $field->id }}-to-show"
style="font-size: 0px;">{{ Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) }}</span>
style="font-size: 0;">{{ Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) }}</span>
@endif
@endif
@else
@@ -57,6 +63,8 @@
<a href="{{ $item->{$field->db_column_name()} }}" target="_new">{{ $item->{$field->db_column_name()} }}</a>
@elseif (($field->format=='DATE') && ($item->{$field->db_column_name()}!=''))
{{ \App\Helpers\Helper::getFormattedDateObject($item->{$field->db_column_name()}, 'date', false) }}
@elseif (($field->element == 'markdown-textarea') && ($item->{$field->db_column_name()} != ''))
<div class="markdown-field-content">{!! Helper::renderMarkdown($item->{$field->db_column_name()}) !!}</div>
@else
{!! nl2br(e($item->{$field->db_column_name()})) !!}
@endif
@@ -63,6 +63,7 @@
'text' => trans('admin/custom_fields/general.types.text'),
'listbox' => trans('admin/custom_fields/general.types.listbox'),
'textarea' => trans('admin/custom_fields/general.types.textarea'),
'markdown-textarea' => trans('admin/custom_fields/general.types.markdown-textarea'),
'checkbox' => trans('admin/custom_fields/general.types.checkbox'),
'radio' => trans('admin/custom_fields/general.types.radio'),
]"
@@ -347,7 +348,7 @@
// and don't display encryption option for checkbox or radio
$(".field_element").change(function(){
$(this).find("option:selected").each(function(){
if (($(this).attr("value")!="text") && ($(this).attr("value")!="textarea")){
if (($(this).attr("value") != "text") && ($(this).attr("value") != "textarea") && ($(this).attr("value") != "markdown-textarea")) {
$("#field_values_text").show();
if ($(this).attr("value") == "checkbox" || $(this).attr("value") == "radio") {
$("#encryption_section").hide();
+34 -12
View File
@@ -2377,6 +2377,8 @@
// Use element id to find the text element to hide / show
var targetElement = e.id+"-to-show";
var hiddenElement = e.id+"-to-hide";
var targetEl = document.getElementById(targetElement);
var isMarkdown = targetEl && targetEl.dataset.markdown;
var audio = new Audio('{{ config('app.url') }}/sounds/lock.mp3');
if($(e).hasClass('fa-lock')) {
@if ((isset($user)) && ($user->enable_sounds))
@@ -2384,7 +2386,11 @@
@endif
$(e).removeClass('fa-lock').addClass('fa-unlock');
// Show the encrypted custom value and hide the element with asterisks
document.getElementById(targetElement).style.fontSize = "100%";
if (isMarkdown) {
targetEl.style.display = "block";
} else {
targetEl.style.fontSize = "100%";
}
document.getElementById(hiddenElement).style.display = "none";
} else {
@@ -2393,7 +2399,12 @@
@endif
$(e).removeClass('fa-unlock').addClass('fa-lock');
// ClipboardJS can't copy display:none elements so use a trick to hide the value
document.getElementById(targetElement).style.fontSize = "0px";
if (isMarkdown) {
targetEl.style.display = "none";
} else {
// ClipboardJS can't copy display:none elements so use a trick to hide the value
targetEl.style.fontSize = "0px";
}
document.getElementById(hiddenElement).style.display = "";
}
@@ -2515,23 +2526,34 @@
// Function to add original value to elements
function addValue($element) {
// Get original value of the element
var originalValue = $element.text().trim();
var originalHtml = $element.html().trim();
var originalText = $element.text().trim();
var hasHtmlContent = originalHtml !== '' && originalHtml !== originalText;
// Show asterisks only for not empty values
if (originalValue !== '') {
// This is necessary to avoid loop because value is generated dynamically
if (originalValue !== '' && originalValue !== asterisks) $element.attr('value', originalValue);
// Show asterisks only for non-empty values
if (originalText !== '') {
var asterisks = '*'.repeat(11);
// Avoid reprocessing already-asterisked elements
if (originalText !== asterisks) {
if (hasHtmlContent) {
$element.data('encrypted-html', originalHtml);
}
$element.attr('value', originalText);
}
// Hide the original value and show asterisks of the same length
var asterisks = '*'.repeat(originalValue.length);
// Hide the original value and show a fixed-length asterisk placeholder
$element.text(asterisks);
// Add click event to show original text
// Add click event to show original value
$element.click(function() {
var $this = $(this);
if ($this.text().trim() === asterisks) {
$this.text($this.attr('value'));
var savedHtml = $this.data('encrypted-html');
if (savedHtml) {
$this.html(savedHtml);
} else {
$this.text($this.attr('value'));
}
} else {
$this.text(asterisks);
}
@@ -16,11 +16,16 @@
<label for="{{ $field->db_column_name() }}" class="col-md-3 control-label">
@if ($field->field_encrypted)
<i class="fas fa-lock" data-tooltip="true" data-placement="top" title="{{ trans('admin/custom_fields/general.value_encrypted') }}"></i>
@endif
{{ $field->name }}
</label>
<div class="col-md-7 col-sm-12">
<div class="col-md-8 col-sm-12">
@if ($field->element!='text')
@@ -36,7 +41,14 @@
@elseif ($field->element=='textarea')
<!-- Textarea -->
<textarea class="col-md-6 form-control" id="{{ $field->db_column_name() }}" name="{{ $field->db_column_name() }}"{{ ($field->pivot->required=='1') ? ' required' : '' }}>{{ old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}</textarea>
<textarea rows="6" class="col-md-6 form-control" id="{{ $field->db_column_name() }}" name="{{ $field->db_column_name() }}"{{ ($field->pivot->required=='1') ? ' required' : '' }}>{{ old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}</textarea>
@elseif ($field->element=='markdown-textarea')
<!-- Markdown Textarea -->
<textarea rows="6" class="col-md-6 form-control" id="{{ $field->db_column_name() }}" name="{{ $field->db_column_name() }}"{{ ($field->pivot->required=='1') ? ' required' : '' }}>{{ old($field->db_column_name(),(isset($item) ? Helper::gracefulDecrypt($field, $item->{$field->db_column_name()}) : $field->defaultValue($model->id))) }}</textarea>
<p class="help-block">
<i class="fab fa-markdown" aria-hidden="true"></i> {{ trans('admin/custom_fields/general.markdown_supported') }}
</p>
@elseif ($field->element=='checkbox')
<!-- Checkbox -->
@@ -108,11 +120,6 @@
?>
</div>
@if ($field->field_encrypted)
<div class="col-md-1 col-sm-1 text-left">
<i class="fas fa-lock" data-tooltip="true" data-placement="top" title="{{ trans('admin/custom_fields/general.value_encrypted') }}"></i>
</div>
@endif
</div>
@endif
@@ -496,7 +496,9 @@
if (cell.is('th')) {
return cell.find('.th-inner').text()
}
return htmlData
// Convert <br> tags to newlines so that line breaks in notes and
// textarea fields survive HTML-stripping during export
return htmlData.replace(/<br\s*\/?>/gi, '\n');
}
// This allows us to override the table defaults set below using the data-dash attributes
@@ -1993,6 +1995,13 @@
return '<a href="mailto:' + row.custom_fields[field_column_plain].value + '" style="white-space: nowrap" data-tooltip="true" title="{{ trans('general.send_email') }}"><x-icon type="email" /> ' + row.custom_fields[field_column_plain].value + '</a>';
}
}
// Convert newlines to <br> for textarea fields so they render in
// the table; export will convert <br> back to \n via onCellHtmlData
if (row.custom_fields[field_column_plain].element === 'textarea') {
var val = row.custom_fields[field_column_plain].value;
return val ? val.replace(/(?:\r\n|\r|\n)/g, '<br>') : '';
}
return row.custom_fields[field_column_plain].value;
}
+2 -1
View File
@@ -381,7 +381,8 @@
@endif
<p class="help-block">
{{ trans('admin/settings/general.dashboard_message_help') }}
{!! trans('general.github_markdown') !!}</p>
<i class="fab fa-markdown" aria-hidden="true"> {!! trans('general.github_markdown') !!}
</p>
</div>
</div>
</fieldset>