diff --git a/ajax/contact/list.php b/ajax/contact/list.php index ef5917fe..149c37d7 100644 --- a/ajax/contact/list.php +++ b/ajax/contact/list.php @@ -29,7 +29,12 @@ if(is_null($aid)) { $active_addressbooks = array(OCA\Contacts\Addressbook::find($aid)); } - +$lastModified = OCA\Contacts\App::lastModified(); +if(!is_null($lastModified)) { + OCP\Response::enableCaching(); + OCP\Response::setLastModifiedHeader($lastModified); + OCP\Response::setETagHeader(md5($lastModified->format('U'))); +} session_write_close(); // create the addressbook associate array diff --git a/css/contacts.css b/css/contacts.css index d9b45bb4..2f1e8e93 100644 --- a/css/contacts.css +++ b/css/contacts.css @@ -5,11 +5,13 @@ height: 14px; width: 14px; border: 1px solid #fff; -moz-appearance:none; -webkit-appearance: none; - -moz-box-sizing:none; box-sizing:none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; -moz-border-radius: 2px; -webkit-border-radius: 2px; border-radius: 2px; } -#content input[type=radio] { width: 12px; height: 12px; } +#content input[type=radio] { + width: 12px; height: 12px; + -moz-border-radius: 4px; -webkit-border-radius: 4px; border-radius: 4px; +} #content input[type=checkbox]:hover { border: 1px solid #D4D4D4 !important; } #content input[type=checkbox]:checked::after { content: url('%appswebroot%/contacts/img/checkmark.png'); @@ -33,17 +35,64 @@ #content input:-ms-input-placeholder { color: #aaa; } #content input:placeholder { color: #aaa; } +#content input:not([type="checkbox"]), #content select, #content textarea { + background-color: #fefefe; border: 1px solid #fff !important; + -moz-appearance:none !important; -webkit-appearance: none !important; + -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; + -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; + -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; + float: left; +} +#content fieldset, #content div, #content span, #content section { + -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; +} +#content fieldset.editor { + border: 1px solid #1d2d44; + -moz-border-radius: 3px; -webkit-border-radius: 3px; border-radius: 3px; outline:none; +} +#content input:invalid, #content input:hover:not([type="checkbox"]), #content input:active:not([type="checkbox"]), #content input:focus:not([type="checkbox"]), #content input.active, #content textarea:focus, #content textarea:hover { + border: 1px solid silver !important; + -moz-border-radius:.3em; -webkit-border-radius:.3em; border-radius:.3em; + outline:none; float: left; +} + /* Left content */ #leftcontent { top: 3.5em !important; padding: 0; margin: 0; } -#leftcontent a { display: inline-block; } -#leftcontent h3 { cursor: pointer; -moz-transition: background 300ms ease 0s; background: none no-repeat scroll 1em center #eee; border-bottom: 1px solid #ddd; border-top: 1px solid #fff; display: block; max-width: 100%; padding: 0.5em 0.8em; color: #666; text-shadow: 0 1px 0 #f8f8f8; font-size: 1.2em; } -#leftcontent h3:hover,#leftcontent h3:active,#leftcontent h3.active { background-color: #DBDBDB; border-bottom: 1px solid #CCCCCC; border-top: 1px solid #D4D4D4; color: #333333; font-weight: bold; } +#leftcontent a { display: inline-block; padding: 0; margin: 0; } +#leftcontent h3 { + cursor: pointer; + -moz-transition: background 300ms ease 0s; + background: none no-repeat scroll 1em center #eee; + border-bottom: 1px solid #ddd; border-top: 1px solid #fff; + display: block; + max-width: 100%; + height: 2em; + padding: 0.5em 0.8em; + color: #666; + text-shadow: 0 1px 0 #f8f8f8; + font-size: 1.2em; +} +#leftcontent h3:hover,#leftcontent h3:active,#leftcontent h3.active { + background-color: #DBDBDB; + border-bottom: 1px solid #CCCCCC; border-top: 1px solid #D4D4D4; + color: #333333; font-weight: bold; +} #leftcontent h3 img.shared { float: right; opacity: 0.4; margin: 0 .5em; } #leftcontent h3 img.shared:hover { opacity: 1; } +#leftcontent h3.editing .checked { margin-left: -25px; opacity: 1; display: inline-block; float: left; } +#leftcontent h3.editing .checked.disabled { opacity: .5 } +#leftcontent h3 input[type="text"] { + display: block; + width: 100%; + font-size: .8em; + float: left; + -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; + margin: auto 0 auto .3em; +} -#groupactions { box-sizing: border-box; height: 4em; border-bottom: 1px solid #DDDDDD; } -#groupactions > button, .addcontact { +#groupactions { -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; height: 4em; border-bottom: 1px solid #DDDDDD; } +#groupactions > button, .addcontact, .import-upload-button, .doImport { -moz-border-bottom-colors: none; -moz-border-left-colors: none; -moz-border-right-colors: none; @@ -60,10 +109,10 @@ } #grouplist { z-index: 100; } -#grouplist h3 .action { float: right; display: none; padding: 0; margin: auto; } -#grouplist h3:not([data-type="shared"]):hover .action.numcontacts, #grouplist h3:not([data-type="shared"]) .active.action.numcontacts { display: inline-block; } -#grouplist h3[data-type="category"]:hover .action.delete { display: inline-block; } -#grouplist h3 .action.delete { width: 20px; height: 20px; } +#grouplist h3 .action:not(.starred):not(.checked):not(.favorite) { float: right; display: none; padding: 0; margin: auto; } +#grouplist h3:not([data-type="shared"]):not(.editing):hover .action.numcontacts, #grouplist h3:not([data-type="shared"]):not(.editing) .active.action.numcontacts { display: inline-block; } +#grouplist h3[data-type="category"]:not(.editing):hover .action.delete { display: inline-block; } +#grouplist h3:not(.editing) .action.delete { width: 20px; height: 20px; } /* First run */ @@ -72,31 +121,16 @@ #firstrun p { font-size:1.2em; text-align:center; } #firstrun #selections { font-size:0.8em; margin: 2em auto auto auto; clear: both; } -#content input:not([type="checkbox"]), #content select, #content textarea { - background-color: #fefefe; border: 1px solid #fff !important; - -moz-appearance:none !important; -webkit-appearance: none !important; - -webkit-box-sizing:none !important; -moz-box-sizing:none !important; box-sizing:none !important; - -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; - -moz-border-radius: 0px; -webkit-border-radius: 0px; border-radius: 0px; - float: left; -} -#content input:invalid, #content input:hover:not([type="checkbox"]), #content input:active:not([type="checkbox"]), #content input:focus:not([type="checkbox"]), #content textarea:focus, #content textarea:hover { - border: 1px solid silver !important; - -moz-border-radius:.3em; -webkit-border-radius:.3em; border-radius:.3em; - outline:none; float: left; -} - #contact { margin: 1em; } #contact textarea { min-height: 5em; min-width: 30em; margin: 0 !important; padding: 0 !important; outline: 0 !important;} #contact input[type="checkbox"] { margin-top: 10px; vertical-align: bottom; float: left; } -dl.form { display: inline-block; width: auto; margin: 0; padding: 0; cursor: normal; } +dl.form { display: block; width: auto; margin: 0; padding: 0; cursor: normal; } .form dt { display: table-cell; clear: left; float: left; width: 7em; margin: 0; padding: 0.8em 0.5em 0 0; text-align:right; text-overflow:ellipsis; o-text-overflow: ellipsis; vertical-align: text-bottom; color: #bbb;/* white-space: pre-wrap; white-space: -moz-pre-wrap !important; white-space: -pre-wrap; white-space: -o-pre-wrap;*/ } .form dd { display: table-cell; clear: right; float: left; margin: 0; padding: 0; white-space: nowrap; vertical-align: text-bottom; } -.action { display: inline-block; width: 20px; height: 20px; } -.action.share { height: 20px !important; width: 20px; float: right !important; clear: right; } +.action { display: inline-block; width: 22px; height: 22px; padding: 0; margin: 0; cursor: pointer; } /* override the default margin on share dropdown */ -#dropdown { margin: 1.5em 0; -moz-box-sizing: border-box; box-sizing: border-box; width: 100%; } +#dropdown { margin: 1.5em 0; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; width: 100%; } .add { background:url('%webroot%/core/img/actions/add.svg') no-repeat center; clear: both; } .delete { background:url('%webroot%/core/img/actions/delete.png') no-repeat center; } .edit { background:url('%webroot%/core/img/actions/rename.svg') no-repeat center; } @@ -106,9 +140,12 @@ dl.form { display: inline-block; width: auto; margin: 0; padding: 0; cursor: nor .download { background:url('%webroot%/core/img/actions/download.svg') no-repeat center; } .cloud { background:url('%webroot%/core/img/places/picture.svg') no-repeat center; } .globe { background:url('%webroot%/core/img/actions/public.svg') no-repeat center; } -.starred { display: inline-block; height: 22px; width: 22px; padding: 0; margin: 0; background:url('%appswebroot%/contacts/img/starred.png') no-repeat center; } +.starred { background:url('%appswebroot%/contacts/img/starred.png') no-repeat center; } +.checked { background:url('%appswebroot%/contacts/img/checkmark-green.png') no-repeat center; } +.checked.disabled { background:url('%appswebroot%/contacts/img/checkmark-gray.png') no-repeat center; cursor: default; } .transparent{ opacity: 0.6; } .float { float: left; display: inline-block; width: auto; } +.float.right { float: right; } .break { clear: both; } .loading { background: url('%webroot%/core/img/loading.gif') no-repeat center !important; /*cursor: progress; */ cursor: wait; } .wait { opacity: cursor: wait; } @@ -129,21 +166,50 @@ dl.form { display: inline-block; width: auto; margin: 0; padding: 0; cursor: nor /* Properties */ -.fullname { font-weight:bold; font-size:1.5em; width: 17em; } -.singleproperties { display: inline-block; float: left; width: 30em;} -.singleproperties input.value { font-weight: bold; } -.singleproperties .action { float: left; width: 20px; height: 20px; } -.singleproperties .n input { width: 17em} -.singleproperties dl { min-width: 24em; } +.singleproperties { display: inline-block; float: left; width: 270px; } +.singleproperties .fullname { font-weight:bold; font-size:1.5em; width: 250px; margin: 5px 0; } +.singleproperties .n.editor { width: 270px; padding: 3px; } +.singleproperties .n.editor input { width: 95%; } +.singleproperties .propertycontainer input.value { font-weight: bold; } +.singleproperties .propertycontainer input.value.new { border: 3px solid #1d2d44;} +.singleproperties .propertycontainer input.value.error { border: 3px solid red;} +.singleproperties .propertycontainer .action { + float: left; + width: 20px; height: 20px; + opacity: 0; +} +.singleproperties .propertycontainer:hover .action { opacity: 1; } +.singleproperties dl { width: 270px; } +.parameters { + width: 120px; + float: left; + text-align: right; + box-sizing: border-box; + display: inline-block; +} + +ul.propertylist { width: 450px; } .propertylist li.propertycontainer { white-space: nowrap; min-width: 38em; display: block; clear: both; } -.propertylist li.propertycontainer > .listactions { display: inline-block; position: absolute; clear: none; opacity: 0; float: right; } -.propertylist li.propertycontainer .listactions a { display: inline-block; float: left; clear: none; width: 20px; height: 20px; } +.propertylist li.propertycontainer > .listactions { + display: inline-block; + position: absolute; + clear: none; opacity: 0; + float: right; +} +.propertylist li.propertycontainer:hover > .listactions { opacity: 1; } +.propertylist li.propertycontainer .listactions a { + display: inline-block; + float: left; clear: none; + width: 20px; height: 20px; +} .propertylist { float: left; } /*.propertylist li > a { display: block; }}*/ .propertylist li > input[type="checkbox"],input[type="radio"] { display: inline-block; } .propertylist input.value:not([type="checkbox"]) { width: 16em; display: inline-block; font-weight: bold; } +.propertylist input.value:not([type="checkbox"]).new { border: 3px solid #1d2d44;} +.propertylist input.value:not([type="checkbox"]).error { border: 3px solid red;} .propertylist li > select { float: left; max-width: 8em; } .select_wrapper { float: left; overflow: hidden; color: #bbb; font-size: 0.8em; } .select_wrapper select { float: left; overflow: hidden; text-overflow: ellipsis; color: #bbb; width: 8em; } @@ -155,7 +221,14 @@ dl.form { display: inline-block; width: auto; margin: 0; padding: 0; cursor: nor .propertylist li > input[type="checkbox"].impp { clear: none; } .propertylist li > label.xab { display: block; color: #bbb; float:left; clear: both; padding: 0.5em 0 0 2.5em; } .propertylist li > label.xab:hover { color: #777; } -#rightcontent label, #rightcontent dt, #rightcontent th, #rightcontent .label { float: left; font-size: 0.7em; font-weight: bold; color: #bbb !important; max-width: 7em !important; border: 0; } +#rightcontent label, #rightcontent dt, #rightcontent th, #rightcontent .label { + float: left; + font-size: 0.7em; font-weight: bold; + color: #bbb !important; + border: 0; + display: inline-block; + box-sizing: border-box; +} #rightcontent label:hover, .form dt:hover, #rightcontent input.label:hover { color: #777 !important; } #rightcontent input.label:hover, #rightcontent input.label:active { border: 0 none !important; border-radius: 0; cursor: pointer; } .typelist[type="button"] { float: left; max-width: 8em; border: 0; background-color: #fff; color: #bbb; box-shadow: none; } /* for multiselect */ @@ -188,13 +261,14 @@ dl.form { display: inline-block; width: auto; margin: 0; padding: 0; cursor: nor #contacts-settings .settings { width: 20px; height: 20px; - float: right; + float: left; background:url('%webroot%/core/img/actions/settings.svg') no-repeat center; } #contacts-settings.open { height: auto; } #contacts-settings { + -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; background: none repeat scroll 0 0 #EEEEEE; @@ -210,31 +284,82 @@ dl.form { display: inline-block; width: auto; margin: 0; padding: 0; cursor: nor z-index: 2; } #contacts-settings li,#contacts-settings li:hover { background-color: transparent; white-space: nowrap; } +#contacts-settings a.action { width: 20px; height: 20px; } +#contacts-settings .actions { float: right; } +.multiselectoptions label { display: block; } /* Single elements */ #file_upload_target, #import_upload_target, #crop_target { display:none; } +#import_fileupload { + height: 2.29em; + /*width: 2.5em;*/ + width: 95%; + margin-top: -2.5em; + display:block; + clear: right; + cursor: pointer; + z-index: 1001; +} +.import-upload-button { + background-image: url("%webroot%/core/img/actions/upload-white.svg"); + background-position: center center; + background-repeat: no-repeat; + cursor: pointer; + height: 2.29em; + width: 2.5em; + z-index: 100; + margin: 0; + cursor: pointer; +} + +.doImport{ margin: auto; } + #toggle_all { position: absolute; bottom: .5em; left: .8em; } -input.propertytype { float: left; font-size: .8em; width: 8em !important; direction: rtl;} +input.propertytype { float: left; font-size: .8em; width: 8em !important; text-align: right; } .contactphoto { float: left; display: inline-block; border-radius: 0.3em; border: thin solid #bbb; margin: 0.5em; background: url('%webroot%/core/img/loading.gif') no-repeat center center; -moz-box-shadow: 0 1px 3px #777; -webkit-box-shadow: 0 1px 3px #777; box-shadow: 0 1px 3px #777; opacity: 1; } .contactphoto:hover { background: #fff; cursor: default; } -#photowrapper { display: inline-block; float: left; width: 150px; } +#photowrapper { + display: inline-block; + position: relative; + float: left; + width: 170px; height: 200px; + margin: 5px; +} + +#photowrapper .favorite { + display: inline-block; + float: right; + position: absolute; + right: -8px; top: -8px; + width: 25px; height: 25px; +} #photowrapper.wait { opacity: 0.6; filter:alpha(opacity=0.6); z-index:1000; background: url('%webroot%/core/img/loading.gif') no-repeat center; cursor: wait; } #phototools { position:absolute; margin: 5px 0 0 10px; width:auto; height:22px; padding:0px; background-color:#fff; list-style-type:none; border-radius: 0.3em; -moz-box-shadow: 0 1px 3px #777; -webkit-box-shadow: 0 1px 3px #777; box-shadow: 0 1px 3px #777; } #phototools li { display: inline; } #phototools li a { float:left; cursor:pointer; width:22px; height:22px; opacity: 0.6; } #phototools li a:hover { opacity: 0.8; } -#contactphoto_fileupload, #import_fileupload { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; z-index:1001; width:0; height:0;} +#contactphoto_fileupload, #import_fileupload { -ms-filter:"progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; filter:alpha(opacity=0); opacity:0; z-index:1001; } .favorite { display: inline-block; float: left; height: 20px; width: 20px; background:url('%appswebroot%/contacts/img/inactive_star.png') no-repeat center; } .favorite.active, .favorite:hover { background:url('%appswebroot%/contacts/img/active_star.png') no-repeat center; } .favorite.inactive { background:url('%appswebroot%/contacts/img/inactive_star.png') no-repeat center; } /* Header */ -#contactsheader { position: fixed; box-sizing: border-box; padding: 0; margin:0; top:3.5em; left: 32.5em; right: 0; height: 4em; border-bottom: 1px solid #DDDDDD; z-index: 50; } -#contactsheader div.actions { padding: 0 0.5em; margin: 0 auto; height: 100%; width: 90%; } +#contactsheader { + position: fixed; + -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; + padding: 0; margin:0; + top:3.5em; left: 32.5em; right: 0; height: 4em; + border-bottom: 1px solid #DDDDDD; z-index: 50; +} +#contactsheader div.actions { + -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; + padding: 0 .5em 0 4em; margin: 0 auto; + height: 100%; width: 100%; +} #contactsheader button, #contactsheader select { position: relative; float: left; min-width: 26px; height: 26px; margin: .7em; padding: .2em; clear: none; } #contactsheader .back { } #contactsheader .import { background:url('%webroot%/core/img/actions/upload.svg') no-repeat center; } -#contactsheader .delete { background:url('%webroot%/core/img/actions/delete.svg') no-repeat center; } +#contactsheader .delete { background:url('%webroot%/core/img/actions/delete.svg') no-repeat center; float: right;} #contactsheader .list.add { margin-left: 5em; } @@ -244,6 +369,7 @@ input.propertytype { float: left; font-size: .8em; width: 8em !important; direct /* Contact layout */ +#contact > ul > li { white-space: nowrap; } #contact > ul.propertylist { font-size: 10px; /*display: table; @@ -256,24 +382,27 @@ input.propertytype { float: left; font-size: .8em; width: 8em !important; direct /*display: table-cell;*/ } +.display .meta { text-align: right; } .display .adr { cursor: pointer; } -.adr.edit { +.adr.editor { width: 20em; - border: 1px solid silver; -moz-border-radius:.3em; -webkit-border-radius:.3em; border-radius:.3em; outline:none; + margin-left: 120px; } -.adr.edit ul { +.adr.editor ul { -moz-column-count: 1; -webkit-columns: 1; -o-columns: 1; columns: 1; } -.adr.edit input.value { border: none; } -.adr.edit input.value:hover { border: none; } -.adr.edit input.value.street, ul.adr.edit input.value.country, ul.adr.edit input.value.region { width: 19em;} -.adr.edit input.value.zip { width: 5em; } -.adr.edit input.value.city { width: 10em; } +.adr.editor input.value { border: none; } +.adr.editor input.value:hover { border: none; } +.adr.editor input.value.street, ul.adr.edit input.value.country, ul.adr.edit input.value.region { width: 19em;} +.adr.editor input.value.zip { width: 5em; } +.adr.editor input.value.city { width: 10em; } -#rightcontent footer { padding: 1em; width: 100%; box-sizing: border-box; clear: both; } +.note { margin-left: 120px; } + +#rightcontent footer { padding: 1em; width: 100%; -moz-box-sizing: border-box; box-sizing: border-box; clear: both; } #rightcontent footer > { display: inline-block; } /* contact list */ @@ -289,6 +418,30 @@ input.propertytype { float: left; font-size: .8em; width: 8em !important; direct #contactlist tr > td.name { font-weight: bold; text-indent: 1.6em; -webkit-transition:background-image 500ms; -moz-transition:background-image 500ms; -o-transition:background-image 500ms; transition:background-image 500ms; position:relative; background-position:1em .5em !important; background-repeat:no-repeat !important; } #contactlist tr > td a.mailto { position: absolute; float: right; clear: none; cursor:pointer; width:22px; height:22px; z-index: 200; opacity: 0.6; background:url('%webroot%/core/img/actions/mail.svg') no-repeat center; } #contactlist tr > td a.mailto:hover { opacity: 0.8; } +#contactlist.dim { background: #ddd; opacity: .50;filter:Alpha(Opacity=50); } + +#contact { + background-color: white; color: #333333; + border-radius: 3px; box-shadow: 0 0 10px #888888; + padding: 10px; + position: fixed !important; + z-index: 200; + top: 8em; left: 35em; + width: 490px; +} +#contact .arrow { + border-bottom: 20px solid white; + border-left: 20px solid transparent; + border-right: 20px solid transparent; + display: block; + height: 0; + position: absolute; + left: -28px; top: 2em; + width: 0; + z-index: 201; + -webkit-transform: rotate(270deg); -moz-transform: rotate(270deg); -o-transform: rotate(270deg); + -ms-transform: rotate(270deg); transform: rotate(270deg); +} #contact figure img { -moz-border-radius:.3em; -webkit-border-radius:.3em; border-radius:.3em; border: thin solid #bbb; margin: 0.3em; background: url('%webroot%/core/img/loading.gif') no-repeat center center; -moz-box-shadow: 0 1px 3px #777; -webkit-box-shadow: 0 1px 3px #777; box-shadow: 0 1px 3px #777; opacity: 1; } @@ -302,6 +455,14 @@ input.propertytype { float: left; font-size: .8em; width: 8em !important; direct } #contact span.adr:hover { /*overflow: inherit;*/ white-space: pre-wrap; } +#contact > ul.propertylist { + -webkit-transition: all 0.5s ease-in-out; + -moz-transition: all 0.5s ease-in-out; + -o-transition: all 0.5s ease-in-out; + -ms-transition: all 0.5s ease-out; + transition: all 0.5s ease-in-out; +} + @media screen and (min-width: 1400px) { #contact > ul.propertylist { -moz-column-count: 3; @@ -310,7 +471,7 @@ input.propertytype { float: left; font-size: .8em; width: 8em !important; direct columns: 3; } } -@media screen and (min-width: 800px) and (max-width: 1400) { +@media screen and (min-width: 800px) and (max-width: 1400px) { #singlevalues { max-width: 50%; } #contact > ul.propertylist { -moz-column-count: 2; diff --git a/css/multiselect.css b/css/multiselect.css new file mode 100644 index 00000000..31c8ef88 --- /dev/null +++ b/css/multiselect.css @@ -0,0 +1,78 @@ +/* Copyright (c) 2011, Jan-Christoph Borchardt, http://jancborchardt.net + This file is licensed under the Affero General Public License version 3 or later. + See the COPYING-README file. */ + + ul.multiselectoptions { + background-color:#fff; + border:1px solid #ddd; + border-top:none; + box-shadow:0 1px 1px #ddd; + padding-top:.5em; + position:absolute; + max-height: 20em; + overflow-y: auto; + z-index:49; + } + + ul.multiselectoptions.down { + border-bottom-left-radius:.5em; + border-bottom-right-radius:.5em; + } + + ul.multiselectoptions.up { + border-top-left-radius:.5em; + border-top-right-radius:.5em; + } + + ul.multiselectoptions>li { + overflow:hidden; + white-space:nowrap; + } + + div.multiselect { + display:inline-block; + max-width:400px; + min-width:100px; + padding-right:.6em; + position:relative; + vertical-align:bottom; + } + + div.multiselect.active { + background-color:#fff; + position:relative; + z-index:50; + } + + div.multiselect.up { + border-top:0 none; + border-top-left-radius:0; + border-top-right-radius:0; + } + + div.multiselect.down { + border-bottom:none; + border-bottom-left-radius:0; + border-bottom-right-radius:0; + } + + div.multiselect>span:first-child { + float:left; + margin-right:2em; + overflow:hidden; + text-overflow:ellipsis; + width:90%; + } + + div.multiselect>span:last-child { + position:absolute; + right:.8em; + } + + ul.multiselectoptions input.new { + border-top-left-radius:0; + border-top-right-radius:0; + padding-bottom:.2em; + padding-top:.2em; + margin:0; + } \ No newline at end of file diff --git a/img/checkmark-gray.png b/img/checkmark-gray.png new file mode 100644 index 00000000..8198e813 Binary files /dev/null and b/img/checkmark-gray.png differ diff --git a/img/checkmark-gray.svg b/img/checkmark-gray.svg new file mode 100644 index 00000000..4692e5a1 --- /dev/null +++ b/img/checkmark-gray.svg @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + diff --git a/img/checkmark-green.png b/img/checkmark-green.png new file mode 100644 index 00000000..9a8a4361 Binary files /dev/null and b/img/checkmark-green.png differ diff --git a/img/checkmark-green.svg b/img/checkmark-green.svg new file mode 100644 index 00000000..bc73c822 --- /dev/null +++ b/img/checkmark-green.svg @@ -0,0 +1,109 @@ + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + diff --git a/img/starred.svg b/img/starred.svg new file mode 100644 index 00000000..ba96cccb --- /dev/null +++ b/img/starred.svg @@ -0,0 +1,78 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + diff --git a/index.php b/index.php index db9f4eda..aef4bacd 100644 --- a/index.php +++ b/index.php @@ -47,7 +47,7 @@ $freeSpace=\OC\Files\Filesystem::free_space('/'); $freeSpace=max($freeSpace, 0); $maxUploadFilesize = min($maxUploadFilesize, $freeSpace); -OCP\Util::addscript('', 'jquery.multiselect'); +OCP\Util::addscript('contacts', 'multiselect'); OCP\Util::addscript('', 'oc-vcategories'); OCP\Util::addscript('contacts', 'app'); OCP\Util::addscript('contacts', 'contacts'); @@ -56,10 +56,10 @@ OCP\Util::addscript('contacts', 'placeholder.polyfill.jquery'); OCP\Util::addscript('contacts', 'expanding'); OCP\Util::addscript('contacts', 'jquery.combobox'); OCP\Util::addscript('files', 'jquery.fileupload'); -OCP\Util::addscript('core', 'jquery.inview'); +//OCP\Util::addscript('core', 'jquery.inview'); OCP\Util::addscript('contacts', 'jquery.Jcrop'); OCP\Util::addscript('contacts', 'jquery.multi-autocomplete'); -OCP\Util::addStyle('', 'jquery.multiselect'); +OCP\Util::addStyle('contacts', 'multiselect'); OCP\Util::addStyle('contacts', 'jquery.combobox'); OCP\Util::addStyle('contacts', 'jquery.Jcrop'); OCP\Util::addStyle('contacts', 'contacts'); diff --git a/js/app.js b/js/app.js index b2afd850..42cd2eb3 100644 --- a/js/app.js +++ b/js/app.js @@ -196,6 +196,9 @@ GroupList.prototype.selectGroup = function(params) { console.log('selectGroup', id, $elem); this.$groupList.find('h3').removeClass('active'); $elem.addClass('active'); + if(id === 'new') { + return; + } this.lastgroup = id; $(document).trigger('status.group.selected', { id: this.lastgroup, @@ -329,6 +332,18 @@ GroupList.prototype.removeFrom = function(contactid, groupid, cb) { var $groupelem = this.findById(groupid); var contacts = $groupelem.data('contacts'); var ids = []; + + // If it's the 'all' group simply decrement the number + if(groupid === 'all') { + var $numelem = $groupelem.find('.numcontacts'); + $numelem.text(parseInt($numelem.text()-1)).switchClass('', 'active', 200); + setTimeout(function() { + $numelem.switchClass('active', '', 1000); + }, 2000); + if(typeof cb === 'function') { + cb({status:'success', ids:[id]}); + } + } // If the contact is in the category remove it from internal list. if(!contacts) { if(typeof cb === 'function') { @@ -443,8 +458,84 @@ GroupList.prototype.deleteGroup = function(groupid, cb) { }); } -GroupList.prototype.addGroup = function(name, cb) { - console.log('GroupList.addGroup', name); +GroupList.prototype.editGroup = function(id) { + var self = this; + // NOTE: Currently this only works for adding, not renaming + var saveChanges = function($elem, $input) { + console.log('saveChanges', $input.val()); + var name = $input.val().trim(); + if(name.length === 0) { + return false; + } + $input.prop('disabled', true); + $elem.data('name', ''); + self.addGroup({name:name, element:$elem}, function(response) { + if(response.status === 'success') { + $elem.prepend(name).removeClass('editing').attr('data-id', response.id); + $input.next('.checked').remove() + $input.remove() + } else { + $input.prop('disabled', false); + OC.notify({message:response.message}); + } + }); + } + + if(typeof id === 'undefined') { + // Add new group + var tmpl = this.$groupListItemTemplate; + var $elem = (tmpl).octemplate({ + id: 'new', + type: 'category', + num: 0, + name: '', + }); + var $input = $(''); + $elem.prepend($input).addClass('editing'); + $elem.data('contacts', []); + this.$groupList.find('h3.group[data-type="category"]').first().before($elem); + this.selectGroup({element:$elem}); + $input.on('input', function(event) { + if($(this).val().length > 0) { + $(this).next('.checked').removeClass('disabled'); + } else { + $(this).next('.checked').addClass('disabled'); + } + }); + $input.on('keyup', function(event) { + var keyCode = Math.max(event.keyCode, event.which); + if(keyCode === 13) { + saveChanges($elem, $(this)); + } else if(keyCode === 27) { + $elem.remove(); + } + }); + $input.next('.checked').on('click keydown', function(event) { + console.log('clicked', event); + if(wrongKey(event)) { + return; + } + saveChanges($elem, $input); + }); + $input.focus(); + } else if(utils.isUInt(id)) { + var $elem = this.findById(id); + var $text = $elem.contents().filter(function(){ return(this.nodeType == 3); }); + var name = $text.text(); + console.log('Group name', $text, name); + $text.remove(); + var $input = $(''); + $elem.data('contacts', contacts).find('.numcontacts').before(''); $elem.droppable({ drop: self.contactDropped, activeClass: 'ui-state-active', @@ -636,7 +732,6 @@ OC.Contacts = OC.Contacts || { this.bindEvents(); this.$toggleAll.show(); this.showActions(['addcontact']); - OC.Share.loadIcons('addressbook'); // Wait 2 mins then check if contacts are indexed. setTimeout(function() { @@ -694,7 +789,8 @@ OC.Contacts = OC.Contacts || { this.$ninjahelp = $('#ninjahelp'); this.$firstRun = $('#firstrun'); this.$settings = $('#contacts-settings'); - + this.$importFileInput = $('#import_fileupload'); + this.$importIntoSelect = $('#import_into'); }, // Build the select to add/remove from groups. buildGroupSelect: function() { @@ -755,7 +851,7 @@ OC.Contacts = OC.Contacts || { $(document).bind('status.contact.added', function(e, data) { self.currentid = parseInt(data.id); self.buildGroupSelect(); - self.showActions(['back', 'download', 'delete', 'groups', 'favorite']); + self.hideActions(); }); $(document).bind('status.contact.error', function(e, data) { @@ -764,16 +860,11 @@ OC.Contacts = OC.Contacts || { $(document).bind('status.contact.enabled', function(e, enabled) { console.log('status.contact.enabled', enabled) - if(enabled) { - self.showActions(['back', 'download', 'delete', 'groups', 'favorite']); + /*if(enabled) { + self.showActions(['back', 'download', 'delete', 'groups']); } else { self.showActions(['back']); - } - if(self.Groups.isFavorite(self.currentid)) { - self.$header.find('.favorite').switchClass('inactive', 'active'); - } else { - self.$header.find('.favorite').switchClass('active', 'inactive'); - } + }*/ }); $(document).bind('status.contacts.loaded', function(e, result) { @@ -838,6 +929,32 @@ OC.Contacts = OC.Contacts || { } }); + $(document).bind('request.contact.setasfavorite', function(e, data) { + var id = parseInt(data.id); + console.log('contact', data.id, 'request.contact.setasfavorite'); + self.Groups.setAsFavorite(data.id, data.state); + }); + + $(document).bind('request.contact.export', function(e, data) { + var id = parseInt(data.id); + console.log('contact', data.id, 'request.contact.export'); + document.location.href = OC.linkTo('contacts', 'export.php') + '?contactid=' + self.currentid; + }); + + $(document).bind('request.contact.close', function(e, data) { + var id = parseInt(data.id); + console.log('contact', data.id, 'request.contact.close'); + self.closeContact(id); + }); + + $(document).bind('request.contact.delete', function(e, data) { + var id = parseInt(data.id); + console.log('contact', data.id, 'request.contact.delete'); + self.Contacts.delayedDelete(id); + self.$contactList.removeClass('dim'); + self.showActions(['add']); + }); + $(document).bind('request.select.contactphoto.fromlocal', function(e, result) { console.log('request.select.contactphoto.fromlocal', result); $('#contactphoto_fileupload').trigger('click'); @@ -906,7 +1023,7 @@ OC.Contacts = OC.Contacts || { if(self.currentid) { var id = self.currentid; self.closeContact(id); - self.Contacts.jumpToContact(id); + self.jumpToContact(id); } self.$contactList.show(); self.$toggleAll.show(); @@ -971,7 +1088,8 @@ OC.Contacts = OC.Contacts || { if(wrongKey(event)) { return; } - self.addGroup(); + self.Groups.editGroup(); + //self.addGroup(); }); this.$ninjahelp.find('.close').on('click keydown',function(event) { @@ -988,7 +1106,7 @@ OC.Contacts = OC.Contacts || { self.buildGroupSelect(); } if(isChecked) { - self.showActions(['addcontact', 'groups', 'delete']); + self.showActions(['addcontact', 'groups', 'delete', 'favorite']); } else { self.showActions(['addcontact']); } @@ -999,7 +1117,7 @@ OC.Contacts = OC.Contacts || { if(self.$groups.find('option').length === 1) { self.buildGroupSelect(); } - self.showActions(['addcontact', 'groups', 'delete']); + self.showActions(['addcontact', 'groups', 'delete', 'favorite']); } else if(self.Contacts.getSelectedContacts().length === 0) { self.showActions(['addcontact']); } @@ -1129,7 +1247,7 @@ OC.Contacts = OC.Contacts || { if($(event.target).is('input')) { return; } - if(event.ctrlKey) { + if(event.ctrlKey || event.metaKey) { event.stopPropagation(); event.preventDefault(); console.log('select', event); @@ -1155,22 +1273,15 @@ OC.Contacts = OC.Contacts || { return; } console.log('add'); - self.$contactList.hide(); self.$toggleAll.hide(); $(this).hide(); self.currentid = 'new'; + var props = { + favorite: false, + groups: self.Groups.categories, + }; self.tmpcontact = self.Contacts.addContact(); self.$rightContent.prepend(self.tmpcontact); - self.showActions(['back']); - }); - - $('.import').on('click keydown', function(event) { - // NOTE: Test if document title changes. If so there's a fix in - // https://github.com/owncloud/apps/pull/212#issuecomment-10516723 - if(wrongKey(event)) { - return; - } - console.log('import'); self.hideActions(); }); @@ -1182,9 +1293,125 @@ OC.Contacts = OC.Contacts || { $(this).next('ul').slideUp(); return; } - console.log('export'); + console.log('settings'); + var $list = $(this).next('ul'); + if($(this).data('id') === 'addressbooks') { + console.log('addressbooks'); + + if(!self.$addressbookTmpl) { + self.$addressbookTmpl = $('#addressbookTemplate'); + } + + $list.empty(); + $.each(self.Contacts.addressbooks, function(id, book) { + var $li = self.$addressbookTmpl.octemplate({ + id: id, + permissions: book.permissions, + displayname: book.displayname, + }); + + $list.append($li); + }); + $list.find('a.action').tipsy({gravity: 'w'}); + $list.find('a.action.delete').on('click keypress', function() { + $('.tipsy').remove(); + var id = parseInt($(this).parents('li').first().data('id')); + console.log('delete', id); + var $li = $(this).parents('li').first(); + $.ajax({ + type:'POST', + url:OC.filePath('contacts', 'ajax', 'addressbook/delete.php'), + data:{ id: id }, + success:function(jsondata) { + console.log(jsondata); + if(jsondata.status == 'success') { + self.Contacts.unsetAddressbook(id); + $li.remove(); + OC.notify({ + message:t('contacts','Deleting done. Click here to cancel reloading.'), + timeout:5, + timeouthandler:function() { + console.log('reloading'); + window.location.href = OC.linkTo('contacts', 'index.php'); + }, + clickhandler:function() { + console.log('reloading cancelled'); + OC.notify({cancel:true}); + } + }); + } else { + OC.notify({message:jsondata.data.message}); + } + }, + error:function(jqXHR, textStatus, errorThrown) { + OC.notify({message:textStatus + ': ' + errorThrown}); + id = false; + }, + }); + }); + $list.find('a.action.globe').on('click keypress', function() { + var id = parseInt($(this).parents('li').first().data('id')); + var book = self.Contacts.addressbooks[id]; + var uri = (book.owner === oc_current_user ) ? book.uri : book.uri + '_shared_by_' + book.owner; + var link = totalurl+'/'+encodeURIComponent(oc_current_user)+'/'+encodeURIComponent(uri); + var $dropdown = $(''); + $dropdown.appendTo($(this).parents('li').first()); + var $input = $dropdown.find('input'); + $input.focus().get(0).select(); + $input.on('blur', function() { + $dropdown.hide('blind', function() { + $dropdown.remove(); + }); + }); + }); + if(typeof OC.Share !== 'undefined') { + OC.Share.loadIcons('addressbook'); + } else { + $list.find('a.action.share').css('display', 'none'); + } + } else if($(this).data('id') === 'import') { + console.log('import'); + $('.import-upload').show(); + $('.import-select').hide(); + + var addAddressbookCallback = function(select, name) { + var id; + $.ajax({ + type:'POST', + async:false, + url:OC.filePath('contacts', 'ajax', 'addressbook/add.php'), + data:{ name: name }, + success:function(jsondata) { + console.log(jsondata); + if(jsondata.status == 'success') { + self.Contacts.setAddressbook(jsondata.data.addressbook); + id = jsondata.data.addressbook.id + } else { + OC.notify({message:jsondata.data.message}); + } + }, + error:function(jqXHR, textStatus, errorThrown) { + OC.notify({message:textStatus + ': ' + errorThrown}); + id = false; + }, + }); + return id; + } + + self.$importIntoSelect.empty(); + $.each(self.Contacts.addressbooks, function(id, book) { + self.$importIntoSelect.append(''); + }); + self.$importIntoSelect.multiSelect({ + createCallback:addAddressbookCallback, + singleSelect: true, + createText:String(t('contacts', 'Add address book')), + minWidth: 120, + }); + + } $(this).parents('ul').first().find('ul:visible').slideUp(); - $(this).next('ul').toggle('slow'); + $list.toggle('slow'); }); this.$header.on('click keydown', '.back', function(event) { @@ -1194,7 +1421,6 @@ OC.Contacts = OC.Contacts || { console.log('back'); self.closeContact(self.currentid); self.$toggleAll.show(); - self.showActions(['addcontact']); }); this.$header.on('click keydown', '.delete', function(event) { @@ -1230,14 +1456,15 @@ OC.Contacts = OC.Contacts || { if(!utils.isUInt(self.currentid)) { return; } + // FIXME: This should only apply for contacts list. var state = self.Groups.isFavorite(self.currentid); console.log('Favorite?', this, state); self.Groups.setAsFavorite(self.currentid, !state, function(jsondata) { if(jsondata.status === 'success') { if(state) { - self.$header.find('.favorite').switchClass('active', 'inactive'); + self.$header.find('.favorite').switchClass('active', ''); } else { - self.$header.find('.favorite').switchClass('inactive', 'active'); + self.$header.find('.favorite').switchClass('', 'active'); } } else { OC.notify({message:t('contacts', jsondata.data.message)}); @@ -1254,19 +1481,210 @@ OC.Contacts = OC.Contacts || { $(this).find('.mailto').fadeOut(100); }); - $(document).on('keyup', function(event) { + // Import using jquery.fileupload + $(function() { + var uploadingFiles = {}, numfiles = 0, uploadedfiles = 0, retries = 0; + var aid, importError = false; + var $progressbar = $('#import-progress'); + var $status = $('#import-status-text'); + + var waitForImport = function() { + if(numfiles == 0 && uploadedfiles == 0) { + $progressbar.progressbar('value',100); + if(!importError) { + OC.notify({ + message:t('contacts','Import done. Click here to cancel reloading.'), + timeout:5, + timeouthandler:function() { + console.log('reloading'); + window.location.href = OC.linkTo('contacts', 'index.php'); + }, + clickhandler:function() { + console.log('reloading cancelled'); + OC.notify({cancel:true}); + } + }); + } + retries = aid = 0; + $progressbar.fadeOut(); + setTimeout(function() { + $status.fadeOut('slow'); + $('.import-upload').show(); + }, 3000); + } else { + setTimeout(function() { + waitForImport(); + }, 1000); + } + }; + var doImport = function(file, aid, cb) { + $.post(OC.filePath('contacts', '', 'import.php'), { id: aid, file: file, fstype: 'OC_FilesystemView' }, + function(jsondata) { + if(jsondata.status != 'success') { + importError = true; + OC.notify({message:jsondata.data.message}); + } + if(typeof cb == 'function') { + cb(jsondata); + } + }); + return false; + }; + + var importFiles = function(aid, uploadingFiles) { + console.log('importFiles', aid, uploadingFiles); + if(numfiles != uploadedfiles) { + OC.notify({message:t('contacts', 'Not all files uploaded. Retrying...')}); + retries += 1; + if(retries > 3) { + numfiles = uploadedfiles = retries = aid = 0; + uploadingFiles = {}; + $progressbar.fadeOut(); + OC.dialogs.alert(t('contacts', 'Something went wrong with the upload, please retry.'), t('contacts', 'Error')); + return; + } + setTimeout(function() { // Just to let any uploads finish + importFiles(aid, uploadingFiles); + }, 1000); + } + $progressbar.progressbar('value', 50); + var todo = uploadedfiles; + $.each(uploadingFiles, function(fileName, data) { + $status.text(t('contacts', 'Importing from {filename}...', {filename:fileName})).fadeIn(); + doImport(fileName, aid, function(response) { + if(response.status === 'success') { + $status.text(t('contacts', '{success} imported, {failed} failed.', + {success:response.data.imported, failed:response.data.failed})).fadeIn(); + } + delete uploadingFiles[fileName]; + numfiles -= 1; uploadedfiles -= 1; + $progressbar.progressbar('value',50+(50/(todo-uploadedfiles))); + }); + }) + //$status.text(t('contacts', 'Importing...')).fadeIn(); + waitForImport(); + }; + + // Start the actual import. + $('.doImport').on('click keypress', function(event) { + if(wrongKey(event)) { + return; + } + aid = $(this).prev('select').val(); + $('.import-select').hide(); + importFiles(aid, uploadingFiles); + }); + + $('#import_fileupload').fileupload({ + acceptFileTypes: /^text\/(directory|vcard|x-vcard)$/i, + add: function(e, data) { + var files = data.files; + var totalSize=0; + if(files) { + numfiles += files.length; uploadedfiles = 0; + for(var i=0;i$('#max_upload').val()) { + OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts','Upload too large')); + numfiles = uploadedfiles = retries = aid = 0; + uploadingFiles = {}; + return; + } else { + if($.support.xhrFileUpload) { + $.each(files, function(i, file) { + var fileName = file.name; + console.log('file.name', file.name); + var jqXHR = $('#import_fileupload').fileupload('send', + { + files: file, + formData: function(form) { + var formArray = form.serializeArray(); + formArray['aid'] = aid; + return formArray; + }}) + .success(function(response, textStatus, jqXHR) { + if(response.status == 'success') { + // import the file + uploadedfiles += 1; + } else { + OC.notify({message:response.data.message}); + } + return false; + }) + .error(function(jqXHR, textStatus, errorThrown) { + console.log(textStatus); + OC.notify({message:errorThrown + ': ' + textStatus,}); + }); + uploadingFiles[fileName] = jqXHR; + }); + } else { + data.submit().success(function(data, status) { + response = jQuery.parseJSON(data[0].body.innerText); + if(response[0] != undefined && response[0].status == 'success') { + var file=response[0]; + delete uploadingFiles[file.name]; + $('tr').filterAttr('data-file',file.name).data('mime',file.mime); + var size = $('tr').filterAttr('data-file',file.name).find('td.filesize').text(); + if(size==t('files','Pending')){ + $('tr').filterAttr('data-file',file.name).find('td.filesize').text(file.size); + } + FileList.loadingDone(file.name); + } else { + OC.notify({message:response.data.message}); + } + }); + } + } + }, + fail: function(e, data) { + console.log('fail'); + OC.notify({message:data.errorThrown + ': ' + data.textStatus}); + // TODO: Remove file from upload queue. + }, + progressall: function(e, data) { + var progress = (data.loaded/data.total)*50; + $progressbar.progressbar('value',progress); + }, + start: function(e, data) { + $progressbar.progressbar({value:0}); + $progressbar.fadeIn(); + if(data.dataType != 'iframe ') { + $('#upload input.stop').show(); + } + }, + stop: function(e, data) { + console.log('stop, data', data); + // stop only gets fired once so we collect uploaded items here. + $('.import-upload').hide(); + $('.import-select').show(); + + if(data.dataType != 'iframe ') { + $('#upload input.stop').hide(); + } + } + }) + }); + + $(document).on('keypress', function(event) { if(event.target.nodeName.toUpperCase() != 'BODY') { return; } + var keyCode = Math.max(event.keyCode, event.which); // TODO: This should go in separate method - console.log(event.which + ' ' + event.target.nodeName); + console.log(event, keyCode + ' ' + event.target.nodeName); /** * To add: * Shift-a: add addressbook * u (85): hide/show leftcontent * f (70): add field */ - switch(event.which) { + switch(keyCode) { case 13: // Enter? console.log('Enter?'); if(!self.currentid && self.currentlistid) { @@ -1334,7 +1752,13 @@ OC.Contacts = OC.Contacts || { }); - $('#content > [title]').tipsy(); // find all with a title attribute and tipsy them + // find all with a title attribute and tipsy them + $('.tooltipped.downwards:not(.onfocus)').tipsy({gravity: 'n'}); + $('.tooltipped.upwards:not(.onfocus)').tipsy({gravity: 's'}); + $('.tooltipped.rightwards:not(.onfocus)').tipsy({gravity: 'w'}); + $('.tooltipped.leftwards:not(.onfocus)').tipsy({gravity: 'e'}); + $('.tooltipped.downwards.onfocus').tipsy({trigger: 'focus', gravity: 'n'}); + $('.tooltipped.rightwards.onfocus').tipsy({trigger: 'focus', gravity: 'w'}); }, addGroup: function(cb) { var self = this; @@ -1351,7 +1775,7 @@ OC.Contacts = OC.Contacts || { buttons: { 'Ok':function() { self.Groups.addGroup( - $dlg.find('input:text').val(), + {name:$dlg.find('input:text').val()}, function(response) { if(typeof cb === 'function') { cb(response); @@ -1384,7 +1808,7 @@ OC.Contacts = OC.Contacts || { }); }, jumpToContact: function(id) { - this.$rightContent.scrollTop(this.Contacts.contactPos(id)); + this.$rightContent.scrollTop(this.Contacts.contactPos(id)+10); }, closeContact: function(id) { if(typeof this.currentid === 'number') { @@ -1396,7 +1820,9 @@ OC.Contacts = OC.Contacts || { this.tmpcontact.remove(); this.$contactList.show(); } + this.$contactList.removeClass('dim'); delete this.currentid; + this.showActions(['addcontact']); this.$groups.find('optgroup,option:not([value="-1"])').remove(); }, openContact: function(id) { @@ -1405,12 +1831,28 @@ OC.Contacts = OC.Contacts || { this.closeContact(this.currentid); } this.currentid = parseInt(id); + console.log('Contacts.openContact, Favorite', this.currentid, this.Groups.isFavorite(this.currentid), this.Groups); this.setAllChecked(false); - this.$contactList.hide(); + //this.$contactList.hide(); + this.$contactList.addClass('dim'); this.$toggleAll.hide(); - var $contactelem = this.Contacts.showContact(this.currentid); + this.jumpToContact(this.currentid); + var props = { + favorite: this.Groups.isFavorite(this.currentid), + groups: this.Groups.categories, + }; + var $contactelem = this.Contacts.showContact(this.currentid, props); + var self = this; + var $contact = $contactelem.find('#contact'); + var adjustElems = function() { + var maxheight = document.documentElement.clientHeight - 200; // - ($contactelem.offset().top+70); + console.log('contact maxheight', maxheight); + $contactelem.find('ul').first().css({'max-height': maxheight, 'overflow-y': 'auto', 'overflow-x': 'hidden'}); + }; + $(window).resize(adjustElems); + //$contact.resizable({ minWidth: 400, minHeight: 400, maxHeight: maxheight}); this.$rightContent.prepend($contactelem); - this.buildGroupSelect(); + adjustElems(); }, update: function() { console.log('update'); @@ -1561,9 +2003,14 @@ OC.Contacts = OC.Contacts || { } }); }, + // NOTE: Deprecated addAddressbook:function(data, cb) { - $.post(OC.filePath('contacts', 'ajax', 'addressbook/add.php'), { name: data.name, description: data.description }, - function(jsondata) { + $.ajax({ + type:'POST', + async:false, + url:OC.filePath('contacts', 'ajax', 'addressbook/add.php'), + data:{ name: data.name, description: data.description }, + success:function(jsondata) { if(jsondata.status == 'success') { if(typeof cb === 'function') { cb({ @@ -1573,11 +2020,12 @@ OC.Contacts = OC.Contacts || { } } else { if(typeof cb === 'function') { - cb({status:'error'}); + cb({status:'error', message:jsondata.data.message}); } } - }); + }}); }, + // NOTE: Deprecated selectAddressbook:function(cb) { var self = this; var jqxhr = $.get(OC.filePath('contacts', 'templates', 'selectaddressbook.html'), function(data) { diff --git a/js/contacts.js b/js/contacts.js index 0fde124d..780d5ea1 100644 --- a/js/contacts.js +++ b/js/contacts.js @@ -1,8 +1,8 @@ OC.Contacts = OC.Contacts || {}; -(function($) { - +(function(window, $, OC) { + 'use strict'; /** * An item which binds the appropriate html and event handlers * @param parent the parent ContactList @@ -46,29 +46,33 @@ OC.Contacts = OC.Contacts || {}; case 'TITLE': case 'ORG': case 'BDAY': - this.$fullelem.find('[data-element="' + name.toLowerCase() + '"]').show(); + case 'NOTE': + this.$fullelem.find('[data-element="' + name.toLowerCase() + '"]').addClass('new').show(); $option.prop('disabled', true); break; case 'TEL': case 'URL': case 'EMAIL': - $elem = this.renderStandardProperty(name.toLowerCase()); + var $elem = this.renderStandardProperty(name.toLowerCase()); var $list = this.$fullelem.find('ul.' + name.toLowerCase()); $list.show(); $list.append($elem); + $elem.find('input.value').addClass('new'); break; case 'ADR': - $elem = this.renderAddressProperty(); + var $elem = this.renderAddressProperty(); var $list = this.$fullelem.find('ul.' + name.toLowerCase()); $list.show(); $list.append($elem); - $elem.find('.adr.display').trigger('click'); + $elem.find('.display').trigger('click'); + $elem.find('input.value').addClass('new'); break; case 'IMPP': - $elem = this.renderIMProperty(); + var $elem = this.renderIMProperty(); var $list = this.$fullelem.find('ul.' + name.toLowerCase()); $list.show(); $list.append($elem); + $elem.find('input.value').addClass('new'); break; } } @@ -87,6 +91,10 @@ OC.Contacts = OC.Contacts || {}; }; if(this.multi_properties.indexOf(element) !== -1) { params['checksum'] = this.checksumFor(obj); + if(params['checksum'] === 'new' && this.valueFor(obj).trim() === '') { + $container.remove(); + return; + } } this.setAsSaving(obj, true); var self = this; @@ -108,9 +116,7 @@ OC.Contacts = OC.Contacts || {}; for(var i in self.data[element]) { if(self.data[element][i].checksum === checksum) { // Found it - var prop = self.data[element][i]; - self.data[element].splice(self.data[element].indexOf(prop), 1); - delete prop; + self.data[element].splice(self.data[element].indexOf(self.data[element][i]), 1); break; } } @@ -150,10 +156,9 @@ OC.Contacts = OC.Contacts || {}; var self = this; this.add({isnew:true}, function(response) { if(!response || response.status === 'error') { - console.log('No response object'); + console.warn('No response object'); return false; } - console.log('Contact added.' + self.id); self.saveProperty(params); }); return; @@ -181,6 +186,7 @@ OC.Contacts = OC.Contacts || {}; status: 'error', message: t('contacts', 'Network or server error. Please inform administrator.'), }); + $(obj).addClass('error'); self.setAsSaving(obj, false); return false; } @@ -204,6 +210,7 @@ OC.Contacts = OC.Contacts || {}; } } } else { + $(obj).removeClass('new'); self.data[element].push({ name: element, value: self.valueFor(obj), @@ -220,6 +227,10 @@ OC.Contacts = OC.Contacts || {}; // We deal with this in addToGroup() break; case 'FN': + if(!self.data.FN || !self.data.FN.length) { + self.data.FN = [{name:'FN', value:'', parameters:[]}] + } + self.data.FN[0]['value'] = value; // Update the list element self.$listelem.find('.nametext').text(value); var nempty = true; @@ -235,7 +246,7 @@ OC.Contacts = OC.Contacts || {}; }); if(nempty) { self.data.N[0]['value'] = ['', '', '', '', '']; - nvalue = value.split(' '); + var nvalue = value.split(' '); // Very basic western style parsing. I'm not gonna implement // https://github.com/android/platform_packages_providers_contactsprovider/blob/master/src/com/android/providers/contacts/NameSplitter.java ;) self.data.N[0]['value'][0] = nvalue.length > 2 && nvalue.slice(nvalue.length-1).toString() || nvalue[1] || ''; @@ -243,7 +254,7 @@ OC.Contacts = OC.Contacts || {}; self.data.N[0]['value'][2] = nvalue.length > 2 && nvalue.slice(1, nvalue.length-1).join(' ') || ''; setTimeout(function() { // TODO: Hint to user to check if name is properly formatted - console.log('auto creating N', self.data.N[0].value) + //console.log('auto creating N', self.data.N[0].value) self.saveProperty({name:'N', value:self.data.N[0].value.join(';')}); setTimeout(function() { self.$fullelem.find('.fullname').next('.action.edit').trigger('click'); @@ -262,10 +273,8 @@ OC.Contacts = OC.Contacts || {}; if(!utils.isArray(value)) { value = value.split(';'); // Then it is auto-generated from FN. - var $nelems = self.$fullelem.find('.n.edit input'); - console.log('nelems', $nelems); + var $nelems = self.$fullelem.find('.n.editor input'); $.each(value, function(idx, val) { - console.log('nval', val); self.$fullelem.find('#n_' + idx).val(val); }); } @@ -273,6 +282,7 @@ OC.Contacts = OC.Contacts || {}; case 'BDAY': case 'ORG': case 'TITLE': + case 'NOTE': self.data[element][0] = { name: element, value: value, @@ -348,14 +358,13 @@ OC.Contacts = OC.Contacts || {}; * @param boolean enabled */ Contact.prototype.setEnabled = function(enabled) { - console.log('setEnabled', enabled); if(enabled) { this.$fullelem.find('#addproperty').show(); } else { this.$fullelem.find('#addproperty').hide(); } this.enabled = enabled; - this.$fullelem.find('.value,.action').each(function () { + this.$fullelem.find('.value,.action,.parameter').each(function () { $(this).prop('disabled', !enabled); }); $(document).trigger('status.contact.enabled', enabled); @@ -443,8 +452,13 @@ OC.Contacts = OC.Contacts || {}; if($(obj).hasClass('propertycontainer')) { q += '&value=' + encodeURIComponent($(obj).val()); } else { - q += '&' + this.propertyContainerFor(obj) - .find('input.value,select.value,textarea.value,.parameter').serialize(); + var $elements = this.propertyContainerFor(obj) + .find('input.value,select.value,textarea.value,.parameter'); + if($elements.length > 1) { + q += '&' + $elements.serialize(); + } else { + q += '&value=' + encodeURIComponent($elements.val()); + } } return q; } @@ -465,10 +479,10 @@ OC.Contacts = OC.Contacts || {}; return $container.is('input') ? $container.val() : (function() { - var $elem = $container.find('input.value:not(:checkbox)'); + var $elem = $container.find('textarea.value,input.value:not(:checkbox)'); console.assert($elem.length > 0, 'Couldn\'t find value for ' + $container.data('element')); if($elem.length === 1) { - return $elem.val; + return $elem.val(); } else if($elem.length > 1) { var retval = []; $.each($elem, function(idx, e) { @@ -499,7 +513,6 @@ OC.Contacts = OC.Contacts || {}; } parameters[paramname].push(val); }); - console.log('Contact.parametersFor', parameters); return parameters; } @@ -534,15 +547,16 @@ OC.Contacts = OC.Contacts || {}; * Render the full contact * @return A jquery object to be inserted in the DOM */ - Contact.prototype.renderContact = function() { + Contact.prototype.renderContact = function(props) { var self = this; var n = this.getPreferredValue('N', ['', '', '', '', '']); - console.log('renderContact', this.data); + //console.log('Contact.renderContact', this.data); var values = this.data ? { id: this.id, + favorite:props.favorite ? 'active' : '', name: this.getPreferredValue('FN', ''), - n0: n[0], n1: n[1], n2: n[2], n3: n[3], n4: n[4], + n0: n[0]||'', n1: n[1]||'', n2: n[2]||'', n3: n[3]||'', n4: n[4]||'', nickname: this.getPreferredValue('NICKNAME', ''), title: this.getPreferredValue('TITLE', ''), org: this.getPreferredValue('ORG', []).clean('').join(', '), // TODO Add parts if more than one. @@ -551,12 +565,17 @@ OC.Contacts = OC.Contacts || {}; $.datepicker.parseDate('yy-mm-dd', this.getPreferredValue('BDAY', '').substring(0, 10))) : '', + note: this.getPreferredValue('NOTE', ''), } - : {id: '', name: '', nickname: '', title: '', org: '', bday: '', n0: '', n1: '', n2: '', n3: '', n4: ''}; + : {id:'', favorite:'', name:'', nickname:'', title:'', org:'', bday:'', note:'', n0:'', n1:'', n2:'', n3:'', n4:''}; this.$fullelem = this.$fullTemplate.octemplate(values).data('contactobject', this); + this.$fullelem.find('.tooltipped.rightwards.onfocus').tipsy({trigger: 'focus', gravity: 'w'}); + this.$fullelem.on('submit', function() { + return false; + }); this.$addMenu = this.$fullelem.find('#addproperty'); this.$addMenu.on('change', function(event) { - console.log('add', $(this).val()); + //console.log('add', $(this).val()); var $opt = $(this).find('option:selected'); self.addProperty($opt, $(this).val()); $(this).val(''); @@ -568,16 +587,15 @@ OC.Contacts = OC.Contacts || {}; $fullname.next('.edit').css('opacity', '0'); }); $fullname.next('.edit').on('click keydown', function(event) { - console.log('edit name', event); + //console.log('edit name', event); $('.tipsy').remove(); if(wrongKey(event)) { return; } $(this).css('opacity', '0'); - var $editor = $(this).next('.n.edit').first(); + var $editor = $(this).next('.n.editor').first(); var bodyListener = function(e) { if($editor.find($(e.target)).length == 0) { - console.log('this', $(this)); $editor.toggle('blind'); $('body').unbind('click', bodyListener); } @@ -586,34 +604,69 @@ OC.Contacts = OC.Contacts || {}; $('body').bind('click', bodyListener); }); }); - var $singleelements = this.$fullelem.find('dd.propertycontainer'); - $singleelements.find('.action').css('opacity', '0'); - $singleelements.on('mouseenter', function() { - $(this).find('.action').css('opacity', '1'); - }).on('mouseleave', function() { - $(this).find('.action').css('opacity', '0'); - }); + this.$fullelem.on('click keydown', '.delete', function(event) { - console.log('delete', event); $('.tipsy').remove(); if(wrongKey(event)) { return; } self.deleteProperty({obj:event.target}); }); + + var $footer = this.$fullelem.find('footer'); + + $footer.on('click keydown', 'button', function(event) { + $('.tipsy').remove(); + if(wrongKey(event)) { + return; + } + if($(this).is('.close')) { + $(document).trigger('request.contact.close', { + id: self.id, + }); + } else if($(this).is('.export')) { + $(document).trigger('request.contact.export', { + id: self.id, + }); + } else if($(this).is('.deletecontact')) { + $(document).trigger('request.contact.delete', { + id: self.id, + }); + } + return false; + }); + this.$fullelem.on('keypress', '.value,.parameter', function(event) { + if(event.keyCode === 13 && $(this).is('input')) { + console.log('Enter'); + $(this).trigger('change'); + return false; + } else if(event.keyCode === 27) { + $(document).trigger('request.contact.close', { + id: self.id, + }); + } + }); + this.$fullelem.on('change', '.value,.parameter', function(event) { - console.log('change', event); self.saveProperty({obj:event.target}); }); - this.$fullelem.find('form').on('submit', function(event) { - console.log('submit', this, event); - return false; - }); this.$fullelem.find('[data-element="bday"]') .find('input').datepicker({ dateFormat : 'dd-mm-yy' }); + this.$fullelem.find('.favorite').on('click', function () { + var state = $(this).hasClass('active'); + if(state) { + $(this).switchClass('active', 'inactive'); + } else { + $(this).switchClass('inactive', 'active'); + } + $(document).trigger('request.contact.setasfavorite', { + id: self.id, + state: !state, + }); + }); this.loadPhoto(); if(!this.data) { // A new contact @@ -623,13 +676,12 @@ OC.Contacts = OC.Contacts || {}; // Loop thru all single occurrence values. If not set hide the // element, if set disable the add menu entry. $.each(values, function(name, value) { - console.log('name', name, 'value', value); if(typeof value === 'undefined') { return true; //continue } + value = value.toString(); if(self.multi_properties.indexOf(value.toUpperCase()) === -1) { if(!value.length) { - console.log('hiding', name); self.$fullelem.find('[data-element="' + name + '"]').hide(); } else { self.$addMenu.find('option[value="' + name.toUpperCase() + '"]').prop('disabled', true); @@ -644,7 +696,7 @@ OC.Contacts = OC.Contacts || {}; if(typeof self.data[name][p] === 'object') { var property = self.data[name][p]; //console.log(name, p, property); - $property = null; + var $property = null; switch(name) { case 'TEL': case 'URL': @@ -681,7 +733,7 @@ OC.Contacts = OC.Contacts || {}; meta.push($cb.attr('title')); } else if(param.toUpperCase() == 'TYPE') { - for(etype in property.parameters[param]) { + for(var etype in property.parameters[param]) { var found = false; var et = property.parameters[param][etype]; if(typeof et !== 'string') { @@ -720,11 +772,6 @@ OC.Contacts = OC.Contacts || {}; singleclick: true, classes: ['propertytype', 'float', 'label'], }); - $property.on('mouseenter', function() { - $(this).find('.listactions').css('opacity', '1'); - }).on('mouseleave', function() { - $(this).find('.listactions').css('opacity', '0'); - }); } $list.append($property); } @@ -759,8 +806,7 @@ OC.Contacts = OC.Contacts || {}; var values = property ? { value: property.value, checksum: property.checksum } : { value: '', checksum: 'new' }; - $elem = this.detailTemplates[name].octemplate(values); - return $elem; + return this.detailTemplates[name].octemplate(values); } /** @@ -768,9 +814,8 @@ OC.Contacts = OC.Contacts || {}; * @return A jquery object to be injected in the DOM */ Contact.prototype.renderAddressProperty = function(idx, property) { - console.log('Contact.renderAddressProperty', property) if(!this.detailTemplates['adr']) { - console.log('No template for adr', this.detailTemplates); + console.warn('No template for adr', this.detailTemplates); return; } if(typeof idx === 'undefined') { @@ -795,20 +840,19 @@ OC.Contacts = OC.Contacts || {}; : {value:'', checksum:'new', adr0:'', adr1:'', adr2:'', adr3:'', adr4:'', adr5:'', adr6:'', idx: idx}; var $elem = this.detailTemplates['adr'].octemplate(values); var self = this; + $elem.find('.tooltipped.downwards:not(.onfocus)').tipsy({gravity: 'n'}); + $elem.find('.tooltipped.rightwards.onfocus').tipsy({trigger: 'focus', gravity: 'w'}); $elem.find('.display').on('click', function() { $(this).next('.listactions').hide(); - var $editor = $(this).siblings('.adr.edit').first(); + var $editor = $(this).siblings('.adr.editor').first(); var $viewer = $(this); var bodyListener = function(e) { if($editor.find($(e.target)).length == 0) { - console.log('this', $(this)); $editor.toggle('blind'); $viewer.slideDown(400, function() { var input = $editor.find('input').first(); - console.log('input', input); var val = self.valueFor(input); var params = self.parametersFor(input, true); - console.log('val', val, 'params', params); $(this).find('.meta').html(params['TYPE'].join('/')); $(this).find('.adr').html(escapeHTML(self.valueFor($editor.find('input').first()).clean('').join(', '))); $(this).next('.listactions').css('display', 'inline-block'); @@ -821,6 +865,64 @@ OC.Contacts = OC.Contacts || {}; $('body').bind('click', bodyListener); }); }); + $elem.find('.value.city') + .autocomplete({ + source: function( request, response ) { + $.ajax({ + url: "http://ws.geonames.org/searchJSON", + dataType: "jsonp", + data: { + featureClass: "P", + style: "full", + maxRows: 12, + lang: lang, + name_startsWith: request.term + }, + success: function( data ) { + response( $.map( data.geonames, function( item ) { + return { + label: item.name + (item.adminName1 ? ", " + item.adminName1 : "") + ", " + item.countryName, + value: item.name, + country: item.countryName + } + })); + } + }); + }, + minLength: 2, + select: function( event, ui ) { + if(ui.item && $elem.find('.value.country').val().trim().length == 0) { + $elem.find('.value.country').val(ui.item.country); + } + }, + }); + $elem.find('.value.country') + .autocomplete({ + source: function( request, response ) { + $.ajax({ + url: "http://ws.geonames.org/searchJSON", + dataType: "jsonp", + data: { + /*featureClass: "A",*/ + featureCode: "PCLI", + /*countryBias: "true",*/ + /*style: "full",*/ + lang: lang, + maxRows: 12, + name_startsWith: request.term + }, + success: function( data ) { + response( $.map( data.geonames, function( item ) { + return { + label: item.name, + value: item.name + } + })); + } + }); + }, + minLength: 2, + }); return $elem; } @@ -830,15 +932,14 @@ OC.Contacts = OC.Contacts || {}; */ Contact.prototype.renderIMProperty = function(property) { if(!this.detailTemplates['impp']) { - console.log('No template for impp', this.detailTemplates); + console.warn('No template for impp', this.detailTemplates); return; } var values = property ? { value: property.value, checksum: property.checksum, } : {value: '', checksum: 'new'}; - $elem = this.detailTemplates['impp'].octemplate(values); - return $elem; + return this.detailTemplates['impp'].octemplate(values); } /** @@ -851,13 +952,12 @@ OC.Contacts = OC.Contacts || {}; this.$photowrapper = this.$fullelem.find('#photowrapper'); this.$photowrapper.addClass('loading').addClass('wait'); var $phototools = this.$fullelem.find('#phototools'); - console.log('photowrapper', this.$photowrapper.length); delete this.photo; $('img.contactphoto').remove() this.photo = new Image(); $(this.photo).load(function () { $(this).addClass('contactphoto'); - self.$photowrapper.css('width', $(this).get(0).width + 10); + self.$photowrapper.css({width: $(this).get(0).width + 10, height: $(this).get(0).height + 10}); self.$photowrapper.removeClass('loading').removeClass('wait'); $(this).insertAfter($phototools).fadeIn(); }).error(function () { @@ -865,7 +965,10 @@ OC.Contacts = OC.Contacts || {}; }).attr('src', OC.linkTo('contacts', 'photo.php')+'?id='+id+refreshstr); if(!dontloadhandlers && this.isEditable()) { - this.$photowrapper.on('mouseenter', function() { + this.$photowrapper.on('mouseenter', function(event) { + if(event.target !== this) { + return; + } $phototools.slideDown(200); }).on('mouseleave', function() { $phototools.slideUp(200); @@ -878,19 +981,16 @@ OC.Contacts = OC.Contacts || {}; $phototools.find('li a').tipsy(); $phototools.find('.edit').on('click', function() { - console.log('TODO: edit photo'); $(document).trigger('request.edit.contactphoto', { id: self.id, }); }); $phototools.find('.cloud').on('click', function() { - console.log('select photo from cloud'); $(document).trigger('request.select.contactphoto.fromcloud', { id: self.id, }); }); $phototools.find('.upload').on('click', function() { - console.log('select photo from local'); $(document).trigger('request.select.contactphoto.fromlocal', { id: self.id, }); @@ -903,7 +1003,6 @@ OC.Contacts = OC.Contacts || {}; $phototools.find('.edit').hide(); } $(document).bind('status.contact.photoupdated', function(e, result) { - console.log('Contact - photoupdated') self.loadPhoto(true); var refreshstr = '&refresh='+Math.random(); self.getListItemElement().find('td.name') @@ -981,18 +1080,10 @@ OC.Contacts = OC.Contacts || {}; if(!this.data.CATEGORIES) { this.data.CATEGORIES = [{value:[name]},]; } else { - var found = false; - $.each(this.data.CATEGORIES[0].value, function(idx, category) { - if(name.toLowerCase() === category.toLowerCase()) { - found = true; - return false; - } - }); - if(found) { + if(this.inGroup(name)) { return; } this.data.CATEGORIES[0].value.push(name); - console.log('listelem categories', this.getPreferredValue('CATEGORIES', []).clean('').join(' / ')); if(this.$listelem) { this.$listelem.find('td.categories') .text(this.getPreferredValue('CATEGORIES', []).clean('').join(' / ')); @@ -1154,13 +1245,14 @@ OC.Contacts = OC.Contacts || {}; } ContactList.prototype.contactPos = function(id) { + console.log('ContactList.contactPos, id', id); + console.trace() if(!id) { - console.log('id missing'); + console.warn('id missing'); return false; } var $elem = this.contacts[parseInt(id)].getListItemElement(); var pos = $elem.offset().top - this.$contactList.offset().top + this.$contactList.scrollTop(); - console.log('pos', pos); return pos; } @@ -1172,16 +1264,6 @@ OC.Contacts = OC.Contacts || {}; this.contacts[parseInt(id)].close(); } - /** - * Jumps to an element in the contact list - * @param number the number of the item starting with 0 - */ - ContactList.prototype.jumpToContact = function(id) { - var pos = this.contactPos(id); - console.log('scrollTop', pos); - this.$contactList.scrollTop(pos); - }; - /** * Returns a Contact object by searching for its id * @param id the id of the node @@ -1286,11 +1368,11 @@ OC.Contacts = OC.Contacts || {}; * @param id the id of the contact * @returns A jquery object to be inserted in the DOM. */ - ContactList.prototype.showContact = function(id) { + ContactList.prototype.showContact = function(id, props) { console.assert(typeof id === 'number', 'ContactList.showContact called with a non-number'); this.currentContact = id; console.log('Contacts.showContact', id, this.contacts[this.currentContact], this.contacts) - return this.contacts[this.currentContact].renderContact(); + return this.contacts[this.currentContact].renderContact(props); }; /** @@ -1298,7 +1380,6 @@ OC.Contacts = OC.Contacts || {}; * @param contact jQuery object. */ ContactList.prototype.insertContact = function($contact) { - console.log('insertContact', $contact); $contact.draggable({ distance: 10, revert: 'invalid', @@ -1324,8 +1405,9 @@ OC.Contacts = OC.Contacts || {}; /** * Add contact + * @param object props */ - ContactList.prototype.addContact = function() { + ContactList.prototype.addContact = function(props) { var contact = new Contact( this, null, @@ -1339,7 +1421,7 @@ OC.Contacts = OC.Contacts || {}; console.assert(typeof this.currentContact == 'number', 'this.currentContact is not a number'); this.contacts[this.currentContact].close(); } - return contact.renderContact(); + return contact.renderContact(props); } /** @@ -1380,6 +1462,29 @@ OC.Contacts = OC.Contacts || {}; }); } + /** + * Save addressbook data + * @param int id + */ + ContactList.prototype.unsetAddressbook = function(id) { + delete this.addressbooks[id]; + } + + /** + * Save addressbook data + * @param object book + */ + ContactList.prototype.setAddressbook = function(book) { + this.addressbooks[parseInt(book.id)] = { + owner: book.userid, + uri: book.uri, + permissions: parseInt(book.permissions), + id: parseInt(book.id), + displayname: book.displayname, + description: book.description, + active: Boolean(parseInt(book.active)), + }; + } /** * Load contacts * @param int offset @@ -1389,17 +1494,10 @@ OC.Contacts = OC.Contacts || {}; // Should the actual ajax call be in the controller? $.getJSON(OC.filePath('contacts', 'ajax', 'contact/list.php'), {offset: offset}, function(jsondata) { if (jsondata && jsondata.status == 'success') { - console.log('ContactList.loadContacts', jsondata.data); + //console.log('ContactList.loadContacts', jsondata.data); self.addressbooks = {}; $.each(jsondata.data.addressbooks, function(i, book) { - self.addressbooks[parseInt(book.id)] = { - owner: book.userid, - permissions: parseInt(book.permissions), - id: parseInt(book.id), - displayname: book.displayname, - description: book.description, - active: Boolean(parseInt(book.active)), - }; + self.setAddressbook(book); }); $.each(jsondata.data.contacts, function(c, contact) { self.contacts[parseInt(contact.id)] @@ -1438,4 +1536,4 @@ OC.Contacts = OC.Contacts || {}; } OC.Contacts.ContactList = ContactList; -})( jQuery ); +})(window, jQuery, OC); diff --git a/js/multiselect.js b/js/multiselect.js new file mode 100644 index 00000000..6bfad9b0 --- /dev/null +++ b/js/multiselect.js @@ -0,0 +1,282 @@ +/** + * @param 'createCallback' A function to be called when a new entry is created. Two arguments are supplied to this function: + * The select element used and the value of the option. If the function returns false addition will be cancelled. If it returns + * anything else it will be used as the value of the newly added option. + * @param 'createText' The placeholder text for the create action. + * @param 'title' The title to show if no options are selected. + * @param 'checked' An array containing values for options that should be checked. Any options which are already selected will be added to this array. + * @param 'labels' The corresponding labels to show for the checked items. + * @param 'oncheck' Callback function which will be called when a checkbox/radiobutton is selected. If the function returns false the input will be unchecked. + * @param 'onuncheck' @see 'oncheck'. + * @param 'singleSelect' If true radiobuttons will be used instead of checkboxes. + */ +(function( $ ){ + var multiSelectId=-1; + $.fn.multiSelect=function(options) { + multiSelectId++; + var settings = { + 'createCallback':false, + 'createText':false, + 'singleSelect':false, + 'title':this.attr('title'), + 'checked':[], + 'labels':[], + 'oncheck':false, + 'onuncheck':false, + 'minWidth': 'default;', + }; + $(this).attr('data-msid', multiSelectId); + $.extend(settings,options); + $.each(this.children(),function(i,option) { + // If the option is selected, but not in the checked array, add it. + if($(option).attr('selected') && settings.checked.indexOf($(option).val()) === -1) { + settings.checked.push($(option).val()); + settings.labels.push($(option).text().trim()); + } + // If the option is in the checked array but not selected, select it. + else if(settings.checked.indexOf($(option).val()) !== -1 && !$(option).attr('selected')) { + $(option).attr('selected', 'selected'); + settings.labels.push($(option).text().trim()); + } + }); + var button=$('
'+settings.title+'
'); + var span=$(''); + span.append(button); + button.data('id',multiSelectId); + button.selectedItems=[]; + this.hide(); + this.before(span); + if(settings.minWidth=='default') { + settings.minWidth=button.width(); + } + button.css('min-width',settings.minWidth); + settings.minOuterWidth=button.outerWidth()-2; + button.data('settings',settings); + + if(!settings.singleSelect && settings.checked.length>0) { + button.children('span').first().text(settings.labels.join(', ')); + } else if(settings.singleSelect) { + button.children('span').first().text(this.find(':selected').text()); + } + + var self = this; + self.menuDirection = 'down'; + button.click(function(event){ + + var button=$(this); + if(button.parent().children('ul').length>0) { + if(self.menuDirection === 'down') { + button.parent().children('ul').slideUp(400,function() { + button.parent().children('ul').remove(); + button.removeClass('active'); + }); + } else { + button.parent().children('ul').fadeOut(400,function() { + button.parent().children('ul').remove(); + button.removeClass('active').removeClass('up'); + }); + } + return; + } + var lists=$('ul.multiselectoptions'); + lists.slideUp(400,function(){ + lists.remove(); + $('div.multiselect').removeClass('active'); + button.addClass('active'); + }); + button.addClass('active'); + event.stopPropagation(); + var options=$(this).parent().next().children(); + var list=$('