2018-11-09 03:01:25 +08:00
import server from "./server.js" ;
import utils from "./utils.js" ;
import messagingService from "./messaging.js" ;
import treeUtils from "./tree_utils.js" ;
import noteAutocompleteService from "./note_autocomplete.js" ;
import treeService from "./tree.js" ;
import linkService from "./link.js" ;
import infoService from "./info.js" ;
import noteDetailService from "./note_detail.js" ;
const $attributeList = $ ( "#attribute-list" ) ;
const $attributeListInner = $ ( "#attribute-list-inner" ) ;
const $promotedAttributesContainer = $ ( "#note-detail-promoted-attributes" ) ;
let attributePromise ;
async function refreshAttributes ( ) {
attributePromise = server . get ( 'notes/' + noteDetailService . getCurrentNoteId ( ) + '/attributes' ) ;
await showAttributes ( ) ;
}
async function getAttributes ( ) {
return await attributePromise ;
}
async function showAttributes ( ) {
$promotedAttributesContainer . empty ( ) ;
2018-11-14 05:14:41 +08:00
$attributeList . hide ( ) ;
$attributeListInner . empty ( ) ;
2018-11-09 03:01:25 +08:00
2018-11-14 02:25:59 +08:00
const note = noteDetailService . getCurrentNote ( ) ;
2018-11-09 03:01:25 +08:00
const attributes = await attributePromise ;
const promoted = attributes . filter ( attr =>
( attr . type === 'label-definition' || attr . type === 'relation-definition' )
&& ! attr . name . startsWith ( "child:" )
&& attr . value . isPromoted ) ;
2018-11-14 05:14:41 +08:00
const hidePromotedAttributes = attributes . some ( attr => attr . type === 'label' && attr . name === 'hidePromotedAttributes' ) ;
if ( promoted . length > 0 && ! hidePromotedAttributes ) {
2018-11-09 03:01:25 +08:00
const $tbody = $ ( "<tbody>" ) ;
for ( const definitionAttr of promoted ) {
const definitionType = definitionAttr . type ;
const valueType = definitionType . substr ( 0 , definitionType . length - 11 ) ;
let valueAttrs = attributes . filter ( el => el . name === definitionAttr . name && el . type === valueType ) ;
if ( valueAttrs . length === 0 ) {
valueAttrs . push ( {
attributeId : "" ,
type : valueType ,
name : definitionAttr . name ,
value : ""
} ) ;
}
if ( definitionAttr . value . multiplicityType === 'singlevalue' ) {
valueAttrs = valueAttrs . slice ( 0 , 1 ) ;
}
for ( const valueAttr of valueAttrs ) {
2018-11-14 02:25:59 +08:00
const $tr = await createPromotedAttributeRow ( definitionAttr , valueAttr ) ;
2018-11-09 03:01:25 +08:00
$tbody . append ( $tr ) ;
}
}
// we replace the whole content in one step so there can't be any race conditions
// (previously we saw promoted attributes doubling)
$promotedAttributesContainer . empty ( ) . append ( $tbody ) ;
}
2018-11-14 02:25:59 +08:00
else if ( note . type !== 'relation-map' ) {
2018-11-09 03:01:25 +08:00
if ( attributes . length > 0 ) {
for ( const attribute of attributes ) {
if ( attribute . type === 'label' ) {
$attributeListInner . append ( utils . formatLabel ( attribute ) + " " ) ;
}
else if ( attribute . type === 'relation' ) {
if ( attribute . value ) {
$attributeListInner . append ( '@' + attribute . name + "=" ) ;
$attributeListInner . append ( await linkService . createNoteLink ( attribute . value ) ) ;
$attributeListInner . append ( " " ) ;
}
else {
messagingService . logError ( ` Relation ${ attribute . attributeId } has empty target ` ) ;
}
}
else if ( attribute . type === 'label-definition' || attribute . type === 'relation-definition' ) {
$attributeListInner . append ( attribute . name + " definition " ) ;
}
else {
messagingService . logError ( "Unknown attr type: " + attribute . type ) ;
}
}
$attributeList . show ( ) ;
}
}
return attributes ;
}
2018-11-14 02:25:59 +08:00
async function createPromotedAttributeRow ( definitionAttr , valueAttr ) {
const definition = definitionAttr . value ;
const $tr = $ ( "<tr>" ) ;
const $labelCell = $ ( "<th>" ) . append ( valueAttr . name ) ;
const $input = $ ( "<input>" )
. prop ( "tabindex" , definitionAttr . position )
. prop ( "attribute-id" , valueAttr . isOwned ? valueAttr . attributeId : '' ) // if not owned, we'll force creation of a new attribute instead of updating the inherited one
. prop ( "attribute-type" , valueAttr . type )
. prop ( "attribute-name" , valueAttr . name )
. prop ( "value" , valueAttr . value )
. addClass ( "form-control" )
. addClass ( "promoted-attribute-input" )
. change ( promotedAttributeChanged ) ;
const $inputCell = $ ( "<td>" ) . append ( $ ( "<div>" ) . addClass ( "input-group" ) . append ( $input ) ) ;
const $actionCell = $ ( "<td>" ) ;
const $multiplicityCell = $ ( "<td>" ) . addClass ( "multiplicity" ) ;
$tr
. append ( $labelCell )
. append ( $inputCell )
. append ( $actionCell )
. append ( $multiplicityCell ) ;
if ( valueAttr . type === 'label' ) {
if ( definition . labelType === 'text' ) {
$input . prop ( "type" , "text" ) ;
// no need to await for this, can be done asynchronously
server . get ( 'attributes/values/' + encodeURIComponent ( valueAttr . name ) ) . then ( attributeValues => {
if ( attributeValues . length === 0 ) {
return ;
}
attributeValues = attributeValues . map ( attribute => { return { value : attribute } ; } ) ;
$input . autocomplete ( {
appendTo : document . querySelector ( 'body' ) ,
hint : false ,
autoselect : true ,
openOnFocus : true ,
minLength : 0
} , [ {
displayKey : 'value' ,
source : function ( term , cb ) {
term = term . toLowerCase ( ) ;
const filtered = attributeValues . filter ( attr => attr . value . toLowerCase ( ) . includes ( term ) ) ;
cb ( filtered ) ;
}
} ] ) ;
} ) ;
}
else if ( definition . labelType === 'number' ) {
$input . prop ( "type" , "number" ) ;
}
else if ( definition . labelType === 'boolean' ) {
$input . prop ( "type" , "checkbox" ) ;
if ( valueAttr . value === "true" ) {
$input . prop ( "checked" , "checked" ) ;
}
}
else if ( definition . labelType === 'date' ) {
$input . prop ( "type" , "date" ) ;
}
else if ( definition . labelType === 'url' ) {
$input . prop ( "placeholder" , "http://website..." ) ;
2018-11-14 18:24:40 +08:00
const $openButton = $ ( "<span>" )
. addClass ( "input-group-text open-external-link-button jam jam-arrow-up-right" )
. prop ( "title" , "Open external link" )
. click ( ( ) => window . open ( $input . val ( ) , '_blank' ) ) ;
2018-11-14 02:25:59 +08:00
2018-11-14 18:24:40 +08:00
$input . after ( $ ( "<div>" )
. addClass ( "input-group-append" )
. append ( $openButton ) ) ;
2018-11-14 02:25:59 +08:00
}
else {
messagingService . logError ( "Unknown labelType=" + definitionAttr . labelType ) ;
}
}
else if ( valueAttr . type === 'relation' ) {
if ( valueAttr . value ) {
$input . val ( await treeUtils . getNoteTitle ( valueAttr . value ) ) ;
}
// no need to wait for this
noteAutocompleteService . initNoteAutocomplete ( $input ) ;
$input . on ( 'autocomplete:selected' , function ( event , suggestion , dataset ) {
promotedAttributeChanged ( event ) ;
} ) ;
2018-11-14 07:05:09 +08:00
$input . setSelectedPath ( valueAttr . value ) ;
2018-11-14 02:25:59 +08:00
}
else {
messagingService . logError ( "Unknown attribute type=" + valueAttr . type ) ;
return ;
}
if ( definition . multiplicityType === "multivalue" ) {
const addButton = $ ( "<span>" )
. addClass ( "jam jam-plus pointer" )
. prop ( "title" , "Add new attribute" )
. click ( async ( ) => {
const $new = await createPromotedAttributeRow ( definitionAttr , {
attributeId : "" ,
type : valueAttr . type ,
name : definitionAttr . name ,
value : ""
} ) ;
$tr . after ( $new ) ;
$new . find ( 'input' ) . focus ( ) ;
} ) ;
const removeButton = $ ( "<span>" )
2018-11-14 18:24:40 +08:00
. addClass ( "jam jam-trash-alt pointer" )
2018-11-14 02:25:59 +08:00
. prop ( "title" , "Remove this attribute" )
. click ( async ( ) => {
if ( valueAttr . attributeId ) {
await server . remove ( "notes/" + noteId + "/attributes/" + valueAttr . attributeId ) ;
}
$tr . remove ( ) ;
} ) ;
2018-11-14 18:24:40 +08:00
$multiplicityCell . append ( addButton ) . append ( " " ) . append ( removeButton ) ;
2018-11-14 02:25:59 +08:00
}
return $tr ;
}
2018-11-09 03:01:25 +08:00
async function promotedAttributeChanged ( event ) {
const $attr = $ ( event . target ) ;
let value ;
if ( $attr . prop ( "type" ) === "checkbox" ) {
value = $attr . is ( ':checked' ) ? "true" : "false" ;
}
else if ( $attr . prop ( "attribute-type" ) === "relation" ) {
2018-11-13 06:34:22 +08:00
const selectedPath = $attr . getSelectedPath ( ) ;
2018-11-09 03:01:25 +08:00
2018-11-13 06:34:22 +08:00
value = selectedPath ? treeUtils . getNoteIdFromNotePath ( selectedPath ) : "" ;
2018-11-09 03:01:25 +08:00
}
else {
value = $attr . val ( ) ;
}
const result = await server . put ( "notes/" + noteDetailService . getCurrentNoteId ( ) + "/attribute" , {
attributeId : $attr . prop ( "attribute-id" ) ,
type : $attr . prop ( "attribute-type" ) ,
name : $attr . prop ( "attribute-name" ) ,
value : value
} ) ;
$attr . prop ( "attribute-id" , result . attributeId ) ;
infoService . showMessage ( "Attribute has been saved." ) ;
}
export default {
getAttributes ,
showAttributes ,
refreshAttributes
}