mirror of https://github.com/keeweb/keeweb
renamed external model to otp-device
This commit is contained in:
parent
92da783e82
commit
68010076f9
|
@ -1,8 +0,0 @@
|
|||
import { Collection } from 'framework/collection';
|
||||
import { ExternalEntryModel } from 'models/external/external-entry-model';
|
||||
|
||||
class ExternalEntryCollection extends Collection {
|
||||
static model = ExternalEntryModel;
|
||||
}
|
||||
|
||||
export { ExternalEntryCollection };
|
|
@ -12,7 +12,7 @@ import { EntryModel } from 'models/entry-model';
|
|||
import { FileInfoModel } from 'models/file-info-model';
|
||||
import { FileModel } from 'models/file-model';
|
||||
import { GroupModel } from 'models/group-model';
|
||||
import { YubiKeyOtpModel } from 'models/external/yubikey-otp-model';
|
||||
import { YubiKeyOtpModel } from 'models/otp-device/yubikey-otp-model';
|
||||
import { MenuModel } from 'models/menu/menu-model';
|
||||
import { PluginManager } from 'plugins/plugin-manager';
|
||||
import { Features } from 'util/features';
|
||||
|
@ -307,12 +307,12 @@ class AppModel {
|
|||
const preparedFilter = this.prepareFilter(filter);
|
||||
const entries = new SearchResultCollection();
|
||||
|
||||
const devicesToMatchOtpEntries = this.files.filter((file) => file.external);
|
||||
const devicesToMatchOtpEntries = this.files.filter((file) => file.backend === 'otp-device');
|
||||
|
||||
const matchedOtpEntrySet = this.settings.yubiKeyMatchEntries ? new Set() : undefined;
|
||||
|
||||
this.files
|
||||
.filter((file) => !file.external)
|
||||
.filter((file) => file.backend !== 'otp-device')
|
||||
.forEach((file) => {
|
||||
file.forEachEntry(preparedFilter, (entry) => {
|
||||
if (matchedOtpEntrySet) {
|
||||
|
@ -827,7 +827,10 @@ class AppModel {
|
|||
this.scheduleBackupFile(file, data);
|
||||
}
|
||||
if (this.settings.yubiKeyAutoOpen) {
|
||||
if (this.attachedYubiKeysCount > 0 && !this.files.some((f) => f.external)) {
|
||||
if (
|
||||
this.attachedYubiKeysCount > 0 &&
|
||||
!this.files.some((f) => f.backend === 'otp-device')
|
||||
) {
|
||||
this.tryOpenOtpDeviceInBackground();
|
||||
}
|
||||
}
|
||||
|
@ -1271,7 +1274,9 @@ class AppModel {
|
|||
}
|
||||
|
||||
const isNewYubiKey = UsbListener.attachedYubiKeys > attachedYubiKeysCount;
|
||||
const hasOpenFiles = this.files.some((file) => file.active && !file.external);
|
||||
const hasOpenFiles = this.files.some(
|
||||
(file) => file.active && file.backend !== 'otp-device'
|
||||
);
|
||||
|
||||
if (isNewYubiKey && hasOpenFiles && !this.openingOtpDevice) {
|
||||
this.tryOpenOtpDeviceInBackground();
|
||||
|
@ -1303,7 +1308,7 @@ class AppModel {
|
|||
return null;
|
||||
}
|
||||
for (const file of this.files) {
|
||||
if (file.external) {
|
||||
if (file.backend === 'otp-device') {
|
||||
const matchingEntry = file.getMatchingEntry(entry);
|
||||
if (matchingEntry) {
|
||||
return matchingEntry;
|
||||
|
|
|
@ -505,7 +505,7 @@ class EntryModel extends Model {
|
|||
}
|
||||
try {
|
||||
this.otpGenerator = Otp.parseUrl(otpUrl);
|
||||
} catch (e) {
|
||||
} catch {
|
||||
this.otpGenerator = null;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -1,21 +0,0 @@
|
|||
import { ExternalDeviceModel } from 'models/external/external-device-model';
|
||||
|
||||
class ExternalOtpDeviceModel extends ExternalDeviceModel {
|
||||
open(callback) {
|
||||
throw 'Not implemented';
|
||||
}
|
||||
|
||||
cancelOpen() {
|
||||
throw 'Not implemented';
|
||||
}
|
||||
|
||||
close(callback) {
|
||||
throw 'Not implemented';
|
||||
}
|
||||
|
||||
getOtp(callback) {
|
||||
throw 'Not implemented';
|
||||
}
|
||||
}
|
||||
|
||||
export { ExternalOtpDeviceModel };
|
|
@ -766,7 +766,11 @@ FileModel.defineModelProperties({
|
|||
oldKeyFileHash: null,
|
||||
oldKeyChangeDate: null,
|
||||
encryptedPassword: null,
|
||||
encryptedPasswordDate: null
|
||||
encryptedPasswordDate: null,
|
||||
supportsTags: true,
|
||||
supportsColors: true,
|
||||
supportsIcons: true,
|
||||
supportsExpiration: true
|
||||
});
|
||||
|
||||
export { FileModel };
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
import { Collection } from 'framework/collection';
|
||||
import { OtpDeviceEntryModel } from './otp-device-entry-model';
|
||||
|
||||
class OtpDeviceEntryCollection extends Collection {
|
||||
static model = OtpDeviceEntryModel;
|
||||
}
|
||||
|
||||
export { OtpDeviceEntryCollection };
|
|
@ -1,7 +1,7 @@
|
|||
import { Model } from 'framework/model';
|
||||
import { EntrySearch } from 'util/entry-search';
|
||||
|
||||
class ExternalEntryModel extends Model {
|
||||
class OtpDeviceEntryModel extends Model {
|
||||
tags = [];
|
||||
fields = {};
|
||||
|
||||
|
@ -44,9 +44,10 @@ class ExternalEntryModel extends Model {
|
|||
}
|
||||
}
|
||||
|
||||
ExternalEntryModel.defineModelProperties({
|
||||
OtpDeviceEntryModel.defineModelProperties({
|
||||
id: '',
|
||||
external: true,
|
||||
file: null,
|
||||
entry: true,
|
||||
readOnly: true,
|
||||
device: undefined,
|
||||
deviceSubId: null,
|
||||
|
@ -59,4 +60,4 @@ ExternalEntryModel.defineModelProperties({
|
|||
_search: undefined
|
||||
});
|
||||
|
||||
export { ExternalEntryModel };
|
||||
export { OtpDeviceEntryModel };
|
|
@ -1,13 +1,11 @@
|
|||
import { Model } from 'framework/model';
|
||||
import { ExternalEntryCollection } from 'collections/external-entry-collection';
|
||||
import { OtpDeviceEntryCollection } from './otp-device-entry-collection';
|
||||
|
||||
class ExternalDeviceModel extends Model {
|
||||
entries = new ExternalEntryCollection();
|
||||
class OtpDeviceModel extends Model {
|
||||
entries = new OtpDeviceEntryCollection();
|
||||
groups = [];
|
||||
entryMap = {};
|
||||
|
||||
close() {}
|
||||
|
||||
forEachEntry(filter, callback) {
|
||||
if (filter.trash || filter.group || filter.tag) {
|
||||
return;
|
||||
|
@ -32,11 +30,28 @@ class ExternalDeviceModel extends Model {
|
|||
this.entryMap[entry.id.toLowerCase()] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
open(callback) {
|
||||
throw 'Not implemented';
|
||||
}
|
||||
|
||||
cancelOpen() {
|
||||
throw 'Not implemented';
|
||||
}
|
||||
|
||||
close(callback) {
|
||||
throw 'Not implemented';
|
||||
}
|
||||
|
||||
getOtp(callback) {
|
||||
throw 'Not implemented';
|
||||
}
|
||||
}
|
||||
|
||||
ExternalDeviceModel.defineModelProperties({
|
||||
OtpDeviceModel.defineModelProperties({
|
||||
id: '',
|
||||
external: true,
|
||||
backend: 'otp-device',
|
||||
skipOpenList: true,
|
||||
readOnly: true,
|
||||
active: false,
|
||||
entries: undefined,
|
||||
|
@ -47,4 +62,4 @@ ExternalDeviceModel.defineModelProperties({
|
|||
entryMap: undefined
|
||||
});
|
||||
|
||||
export { ExternalDeviceModel };
|
||||
export { OtpDeviceModel };
|
|
@ -1,6 +1,6 @@
|
|||
import { ExternalEntryModel } from 'models/external/external-entry-model';
|
||||
import { OtpDeviceEntryModel } from './otp-device-entry-model';
|
||||
|
||||
class ExternalOtpEntryModel extends ExternalEntryModel {
|
||||
class OtpEntryModel extends OtpDeviceEntryModel {
|
||||
constructor(props) {
|
||||
super({
|
||||
...props,
|
||||
|
@ -59,10 +59,11 @@ class ExternalOtpEntryModel extends ExternalEntryModel {
|
|||
}
|
||||
}
|
||||
|
||||
ExternalOtpEntryModel.defineModelProperties({
|
||||
OtpEntryModel.defineModelProperties({
|
||||
user: undefined,
|
||||
backend: 'otp-device',
|
||||
otpGenerator: undefined,
|
||||
needsTouch: false
|
||||
});
|
||||
|
||||
export { ExternalOtpEntryModel };
|
||||
export { OtpEntryModel };
|
|
@ -1,13 +1,13 @@
|
|||
import { Events } from 'framework/events';
|
||||
import { ExternalOtpDeviceModel } from 'models/external/external-otp-device-model';
|
||||
import { ExternalOtpEntryModel } from 'models/external/external-otp-entry-model';
|
||||
import { OtpDeviceModel } from './otp-device-model';
|
||||
import { OtpEntryModel } from './otp-entry-model';
|
||||
import { Logger } from 'util/logger';
|
||||
import { UsbListener } from 'comp/app/usb-listener';
|
||||
import { YubiKey } from 'comp/app/yubikey';
|
||||
|
||||
const logger = new Logger('yubikey');
|
||||
|
||||
class YubiKeyOtpModel extends ExternalOtpDeviceModel {
|
||||
class YubiKeyOtpModel extends OtpDeviceModel {
|
||||
constructor(props) {
|
||||
super({
|
||||
id: 'yubikey',
|
||||
|
@ -67,8 +67,9 @@ class YubiKeyOtpModel extends ExternalOtpDeviceModel {
|
|||
|
||||
for (const code of codes) {
|
||||
this.entries.push(
|
||||
new ExternalOtpEntryModel({
|
||||
new OtpEntryModel({
|
||||
id: this.entryId(code.title, code.user),
|
||||
file: this,
|
||||
device: this,
|
||||
deviceSubId: serial,
|
||||
icon: 'clock',
|
|
@ -14,9 +14,6 @@ EntryPresenter.prototype = {
|
|||
this.entry = item;
|
||||
} else if (item.group) {
|
||||
this.group = item;
|
||||
} else if (item.external) {
|
||||
this.entry = item;
|
||||
this.external = true;
|
||||
}
|
||||
return this;
|
||||
},
|
||||
|
@ -71,7 +68,7 @@ EntryPresenter.prototype = {
|
|||
if (!this.entry) {
|
||||
return '[' + Locale.listGroup + ']';
|
||||
}
|
||||
if (this.external) {
|
||||
if (this.entry.backend === 'otp-device') {
|
||||
return this.entry.description;
|
||||
}
|
||||
switch (this.descField) {
|
||||
|
|
|
@ -616,7 +616,7 @@ class AppView extends View {
|
|||
|
||||
closeAllFilesAndShowFirst() {
|
||||
let fileToShow = this.model.files.find(
|
||||
(file) => !file.demo && !file.created && !file.external
|
||||
(file) => !file.demo && !file.created && !file.skipOpenList
|
||||
);
|
||||
this.model.closeAllFiles();
|
||||
if (!fileToShow) {
|
||||
|
|
|
@ -21,7 +21,7 @@ function createDetailsFields(detailsView) {
|
|||
const fieldViews = [];
|
||||
const fieldViewsAside = [];
|
||||
|
||||
if (model.external) {
|
||||
if (model.backend === 'otp-device') {
|
||||
fieldViewsAside.push(
|
||||
new FieldViewReadOnly({
|
||||
name: 'Device',
|
||||
|
@ -73,15 +73,27 @@ function createDetailsFields(detailsView) {
|
|||
})
|
||||
);
|
||||
} else {
|
||||
fieldViewsAside.push(
|
||||
new FieldViewReadOnly({
|
||||
name: 'File',
|
||||
title: StringFormat.capFirst(Locale.file),
|
||||
value() {
|
||||
return model.fileName;
|
||||
}
|
||||
})
|
||||
);
|
||||
if (model.backend) {
|
||||
fieldViewsAside.push(
|
||||
new FieldViewReadOnly({
|
||||
name: 'Storage',
|
||||
title: StringFormat.capFirst(Locale.storage),
|
||||
value() {
|
||||
return model.fileName;
|
||||
}
|
||||
})
|
||||
);
|
||||
} else {
|
||||
fieldViewsAside.push(
|
||||
new FieldViewReadOnly({
|
||||
name: 'File',
|
||||
title: StringFormat.capFirst(Locale.file),
|
||||
value() {
|
||||
return model.fileName;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
fieldViews.push(
|
||||
new FieldViewAutocomplete({
|
||||
|
@ -127,26 +139,30 @@ function createDetailsFields(detailsView) {
|
|||
sequence: '{NOTES}'
|
||||
})
|
||||
);
|
||||
fieldViews.push(
|
||||
new FieldViewTags({
|
||||
name: 'Tags',
|
||||
title: StringFormat.capFirst(Locale.tags),
|
||||
tags: AppModel.instance.tags,
|
||||
value() {
|
||||
return model.tags;
|
||||
}
|
||||
})
|
||||
);
|
||||
fieldViews.push(
|
||||
new FieldViewDate({
|
||||
name: 'Expires',
|
||||
title: Locale.detExpires,
|
||||
lessThanNow: '(' + Locale.detExpired + ')',
|
||||
value() {
|
||||
return model.expires;
|
||||
}
|
||||
})
|
||||
);
|
||||
if (model.file.supportsTags) {
|
||||
fieldViews.push(
|
||||
new FieldViewTags({
|
||||
name: 'Tags',
|
||||
title: StringFormat.capFirst(Locale.tags),
|
||||
tags: AppModel.instance.tags,
|
||||
value() {
|
||||
return model.tags;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
if (model.file.supportsExpiration) {
|
||||
fieldViews.push(
|
||||
new FieldViewDate({
|
||||
name: 'Expires',
|
||||
title: Locale.detExpires,
|
||||
lessThanNow: '(' + Locale.detExpired + ')',
|
||||
value() {
|
||||
return model.expires;
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
fieldViewsAside.push(
|
||||
new FieldViewReadOnly({
|
||||
name: 'Group',
|
||||
|
@ -159,33 +175,39 @@ function createDetailsFields(detailsView) {
|
|||
}
|
||||
})
|
||||
);
|
||||
fieldViewsAside.push(
|
||||
new FieldViewReadOnly({
|
||||
name: 'Created',
|
||||
title: Locale.detCreated,
|
||||
value() {
|
||||
return DateFormat.dtStr(model.created);
|
||||
}
|
||||
})
|
||||
);
|
||||
fieldViewsAside.push(
|
||||
new FieldViewReadOnly({
|
||||
name: 'Updated',
|
||||
title: Locale.detUpdated,
|
||||
value() {
|
||||
return DateFormat.dtStr(model.updated);
|
||||
}
|
||||
})
|
||||
);
|
||||
fieldViewsAside.push(
|
||||
new FieldViewHistory({
|
||||
name: 'History',
|
||||
title: StringFormat.capFirst(Locale.history),
|
||||
value() {
|
||||
return { length: model.historyLength, unsaved: model.unsaved };
|
||||
}
|
||||
})
|
||||
);
|
||||
if (model.created) {
|
||||
fieldViewsAside.push(
|
||||
new FieldViewReadOnly({
|
||||
name: 'Created',
|
||||
title: Locale.detCreated,
|
||||
value() {
|
||||
return DateFormat.dtStr(model.created);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
if (model.updated) {
|
||||
fieldViewsAside.push(
|
||||
new FieldViewReadOnly({
|
||||
name: 'Updated',
|
||||
title: Locale.detUpdated,
|
||||
value() {
|
||||
return DateFormat.dtStr(model.updated);
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
if (!model.backend) {
|
||||
fieldViewsAside.push(
|
||||
new FieldViewHistory({
|
||||
name: 'History',
|
||||
title: StringFormat.capFirst(Locale.history),
|
||||
value() {
|
||||
return { length: model.historyLength, unsaved: model.unsaved };
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
if (otpEntry) {
|
||||
fieldViews.push(
|
||||
new FieldViewOtp({
|
||||
|
@ -207,7 +229,7 @@ function createDetailsFields(detailsView) {
|
|||
fieldViews.push(
|
||||
new FieldViewOtp({
|
||||
name: '$' + field,
|
||||
title: field,
|
||||
title: Locale.detOtpField,
|
||||
value() {
|
||||
return model.otpGenerator;
|
||||
},
|
||||
|
|
|
@ -121,6 +121,9 @@ class DetailsView extends View {
|
|||
}
|
||||
const model = {
|
||||
deleted: this.appModel.filter.trash,
|
||||
canEditColor: this.model.file.supportsColors && !this.model.readOnly,
|
||||
canEditIcon: this.model.file.supportsIcons && !this.model.readOnly,
|
||||
showButtons: !this.model.backend && !this.model.readOnly,
|
||||
...this.model
|
||||
};
|
||||
this.template = template;
|
||||
|
@ -185,7 +188,7 @@ class DetailsView extends View {
|
|||
|
||||
this.fieldViews = fieldViews.concat(fieldViewsAside);
|
||||
|
||||
if (!this.model.external) {
|
||||
if (!this.model.backend) {
|
||||
this.moreView = new DetailsAddFieldView();
|
||||
this.moreView.render();
|
||||
this.moreView.on('add-field', this.addNewField.bind(this));
|
||||
|
@ -363,7 +366,7 @@ class DetailsView extends View {
|
|||
}
|
||||
|
||||
toggleIcons() {
|
||||
if (this.model.external) {
|
||||
if (this.model.backend) {
|
||||
return;
|
||||
}
|
||||
if (this.views.sub && this.views.sub instanceof IconSelectView) {
|
||||
|
@ -464,7 +467,7 @@ class DetailsView extends View {
|
|||
}
|
||||
|
||||
this.model.initOtpGenerator?.();
|
||||
if (this.model.external) {
|
||||
if (this.model.backend === 'otp-device') {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -496,7 +499,7 @@ class DetailsView extends View {
|
|||
if (!this.model) {
|
||||
return;
|
||||
}
|
||||
if (this.model.external) {
|
||||
if (this.model.backend === 'otp-device') {
|
||||
this.copyOtp();
|
||||
e.preventDefault();
|
||||
}
|
||||
|
@ -520,7 +523,7 @@ class DetailsView extends View {
|
|||
|
||||
copyOtp() {
|
||||
const otpField = this.getFieldView('$otp');
|
||||
if (this.model.external) {
|
||||
if (this.model.backend === 'otp-device') {
|
||||
if (!otpField) {
|
||||
return false;
|
||||
}
|
||||
|
@ -715,7 +718,7 @@ class DetailsView extends View {
|
|||
}
|
||||
|
||||
editTitle() {
|
||||
if (this.model.external) {
|
||||
if (this.model.backend === 'otp-device') {
|
||||
return;
|
||||
}
|
||||
const input = $('<input/>')
|
||||
|
@ -875,7 +878,7 @@ class DetailsView extends View {
|
|||
const canCopy = document.queryCommandSupported('copy');
|
||||
const options = [];
|
||||
if (canCopy) {
|
||||
if (this.model.external) {
|
||||
if (this.model.backend === 'otp-device') {
|
||||
options.push({
|
||||
value: 'det-copy-otp',
|
||||
icon: 'copy',
|
||||
|
@ -894,7 +897,7 @@ class DetailsView extends View {
|
|||
text: Locale.detMenuCopyUser
|
||||
});
|
||||
}
|
||||
if (!this.model.external) {
|
||||
if (!this.model.backend) {
|
||||
options.push({ value: 'det-add-new', icon: 'plus', text: Locale.detMenuAddNewField });
|
||||
options.push({ value: 'det-clone', icon: 'clone', text: Locale.detClone });
|
||||
if (canCopy) {
|
||||
|
@ -985,7 +988,8 @@ class DetailsView extends View {
|
|||
|
||||
autoType(sequence) {
|
||||
const entry = this.model;
|
||||
const hasOtp = sequence?.includes('{TOTP}') || (entry.external && !sequence);
|
||||
const hasOtp =
|
||||
sequence?.includes('{TOTP}') || (entry.backend === 'otp-device' && !sequence);
|
||||
if (hasOtp) {
|
||||
const otpField = this.getFieldView('$otp');
|
||||
otpField.refreshOtp((err) => {
|
||||
|
|
|
@ -5,7 +5,7 @@ class FieldViewReadOnly extends FieldView {
|
|||
readonly = true;
|
||||
|
||||
renderValue(value) {
|
||||
value = value.isProtected ? new Array(value.textLength + 1).join('•') : escape(value);
|
||||
value = value?.isProtected ? new Array(value.textLength + 1).join('•') : escape(value);
|
||||
value = value.replace(/\n/g, '<br/>');
|
||||
return value;
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import { View } from 'framework/views/view';
|
||||
import { Events } from 'framework/events';
|
||||
import template from 'templates/settings/settings-file-external.hbs';
|
||||
import template from 'templates/settings/settings-file-otp-device.hbs';
|
||||
|
||||
class SettingsFileExternalView extends View {
|
||||
class SettingsFileOtpDeviceView extends View {
|
||||
template = template;
|
||||
|
||||
events = {
|
||||
|
@ -26,4 +26,4 @@ class SettingsFileExternalView extends View {
|
|||
}
|
||||
}
|
||||
|
||||
export { SettingsFileExternalView };
|
||||
export { SettingsFileOtpDeviceView };
|
|
@ -33,8 +33,8 @@ class SettingsView extends View {
|
|||
|
||||
setPage(e) {
|
||||
let { page, section, file } = e;
|
||||
if (page === 'file' && file && file.external) {
|
||||
page = 'file-external';
|
||||
if (page === 'file' && file && file.backend === 'otp-device') {
|
||||
page = 'file-otp-device';
|
||||
}
|
||||
const module = require('./settings-' + page + '-view');
|
||||
const viewName = StringFormat.pascalCase(page);
|
||||
|
|
|
@ -4,11 +4,11 @@
|
|||
</div>
|
||||
<div class="details__header">
|
||||
<i class="details__header-color fa fa-bookmark-o"
|
||||
{{#unless readOnly}}
|
||||
{{#if canEditColor}}
|
||||
title="{{res 'detSetIconColor'}}" tip-placement="left"
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
>
|
||||
{{#unless readOnly}}
|
||||
{{#if canEditColor}}
|
||||
<span class="details__colors-popup">
|
||||
<span class="details__colors-popup-item yellow-color fa fa-bookmark-o" data-color="yellow"></span>
|
||||
<span class="details__colors-popup-item green-color fa fa-bookmark-o" data-color="green"></span>
|
||||
|
@ -17,18 +17,18 @@
|
|||
<span class="details__colors-popup-item blue-color fa fa-bookmark-o" data-color="blue"></span>
|
||||
<span class="details__colors-popup-item violet-color fa fa-bookmark-o" data-color="violet"></span>
|
||||
</span>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
</i>
|
||||
<h1 class="details__header-title">{{#if title}}{{title}}{{else}}(no title){{/if}}</h1>
|
||||
{{#if customIcon}}
|
||||
<div class="details__header-icon details__header-icon--icon"
|
||||
{{#unless readOnly}}title="{{res 'detSetIcon'}}"{{/unless}}
|
||||
{{#if canEditIcon}}title="{{res 'detSetIcon'}}"{{/if}}
|
||||
>
|
||||
<img class="details__header-icon-img" src="{{customIcon}}" />
|
||||
</div>
|
||||
{{else}}
|
||||
<i class="details__header-icon fa fa-{{icon}}"
|
||||
{{#unless readOnly}}title="{{res 'detSetIcon'}}"{{/unless}}
|
||||
{{#if canEditIcon}}title="{{res 'detSetIcon'}}"{{/if}}
|
||||
></i>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
@ -42,9 +42,9 @@
|
|||
</div>
|
||||
<div class="scroller__bar-wrapper"><div class="scroller__bar"></div></div>
|
||||
</div>
|
||||
{{#unless readOnly}}
|
||||
<div class="details__issues-container">
|
||||
</div>
|
||||
<div class="details__issues-container">
|
||||
</div>
|
||||
{{#if showButtons}}
|
||||
<div class="details__buttons">
|
||||
{{#if deleted~}}
|
||||
<i class="details__buttons-trash-del fa fa-minus-circle" title="{{res 'detDelEntryPerm'}}" tip-placement="top"></i>
|
||||
|
@ -67,7 +67,7 @@
|
|||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
<div class="details__dropzone">
|
||||
<i class="fa fa-paperclip muted-color details__dropzone-icon"></i>
|
||||
<h1 class="muted-color details__dropzone-header">{{res 'detDropAttachments'}}</h1>
|
||||
|
|
Loading…
Reference in New Issue