From 0d25173c8f0c28afbc0a44233107f34fe18256bd Mon Sep 17 00:00:00 2001 From: chenten16 Date: Sat, 1 Oct 2022 11:36:05 +0100 Subject: [PATCH 01/10] [Asana] fixing board view button --- scripts-2.0/style/asana.scss | 11 ++++++++++- scripts-2.0/third_party/asana.js | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/scripts-2.0/style/asana.scss b/scripts-2.0/style/asana.scss index e1a7c98..510f08a 100644 --- a/scripts-2.0/style/asana.scss +++ b/scripts-2.0/style/asana.scss @@ -23,7 +23,16 @@ } } -.BaseCard:hover { +//board view +.BoardCard--hover{ + .tc-button.asana { + &__board-view { + opacity: 1; + } + } +} + +.BoardCardLayout.tc:hover{ .tc-button.asana { &__board-view { opacity: 1; diff --git a/scripts-2.0/third_party/asana.js b/scripts-2.0/third_party/asana.js index 7dd5b9a..e9448f7 100644 --- a/scripts-2.0/third_party/asana.js +++ b/scripts-2.0/third_party/asana.js @@ -8,7 +8,7 @@ const buildExternalIdForAsana = (taskId) => { //Board view tcbutton.render( - '.BaseCard .BoardCardLayout:not(.tc)', + '.BoardCardLayout:not(.tc)', {observe: true,}, elem => { if ($('.tc-button', elem)) { From 28aaacaf8a05c01ccd848a6cdab0d8cb7df04d7a Mon Sep 17 00:00:00 2001 From: chenten16 Date: Sat, 1 Oct 2022 13:23:19 +0100 Subject: [PATCH 02/10] [WRIKE] fixing integration with wrike --- scripts/third_party/wrike.js | 13 +++---------- styles/sites/wrike.css | 14 +++++++++++--- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/scripts/third_party/wrike.js b/scripts/third_party/wrike.js index f21ee98..7268c6b 100644 --- a/scripts/third_party/wrike.js +++ b/scripts/third_party/wrike.js @@ -14,18 +14,11 @@ function WrikeTimer() { } var url = document.URL; - var taskOpenPattern = /&t=([0-9]+)/ig; - var taskFromKanbanPattern = /task\/([0-9]+)/ig; + var taskOpenPattern = /&sidePanelItemId=([0-9]+)/ig; var MatchRes; MatchRes = taskOpenPattern.exec(url); - console.log("timecamp", MatchRes, url, "match1"); - - if (!MatchRes) { - MatchRes = taskFromKanbanPattern.exec(url); - console.log("timecamp", MatchRes, url, "match2"); - } if (MatchRes) { var params = { v2_task_id: MatchRes[1], @@ -46,9 +39,9 @@ function WrikeTimer() { this.currentTaskName = function () { - var el = $("#entityname"); + var el = $("task-title textarea"); if (el.length) - return el.html(); + return el.val(); return false; }; diff --git a/styles/sites/wrike.css b/styles/sites/wrike.css index 96d0f40..34aded6 100644 --- a/styles/sites/wrike.css +++ b/styles/sites/wrike.css @@ -2,7 +2,7 @@ margin-left: 2px; } -#timecamp-track-button { +#timecamp-track-button { padding: 4px 18px 0 0 !important; font-size: 12px; cursor: pointer; @@ -24,6 +24,12 @@ margin-left: 5px; } +#timecamp-track-button { + display: flex; + justify-content: center; + align-items: center; +} + #tc-badge { padding: 1px 4px; height: 14px; @@ -40,8 +46,10 @@ .button-link { border: 1px solid #f0f0f0; - -webkit-border-radius: 5px;; - -moz-border-radius: 5px;; + -webkit-border-radius: 5px; + ; + -moz-border-radius: 5px; + ; border-radius: 5px; cursor: pointer; vertical-align: middle; From b44659f0a7bdf6ea4f1ba8af189988d57ac45a6b Mon Sep 17 00:00:00 2001 From: chenten16 Date: Mon, 3 Oct 2022 17:09:23 +0100 Subject: [PATCH 03/10] editing version --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e3a4a81..8e7fca8 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "timecamp-chrome-extension", - "version": "2.52.0", + "version": "2.52.37", "description": "TimeCamp Google Chrome Time Tracking Extension", "main": "index.js", "scripts": { From e82b80fdbf09f4d8056b62ef8c5e8a2593fe1ec6 Mon Sep 17 00:00:00 2001 From: chenten16 Date: Mon, 3 Oct 2022 17:11:14 +0100 Subject: [PATCH 04/10] [TODOIST] switching from clockify to toggl --- integrationList.json | 5 +++++ manifest.json | 10 ---------- 2 files changed, 5 insertions(+), 10 deletions(-) diff --git a/integrationList.json b/integrationList.json index f90d033..444f321 100644 --- a/integrationList.json +++ b/integrationList.json @@ -4,6 +4,11 @@ "script": ["asana.js"], "isActiveInProd": true }, + { + "matches": ["*://*.todoist.com/app*"], + "script": ["todoist.js"], + "isActiveInProd": true + }, { "matches": ["*://docs.google.com/*"], "script": ["google-docs.js"], diff --git a/manifest.json b/manifest.json index e80cfef..4581593 100644 --- a/manifest.json +++ b/manifest.json @@ -83,16 +83,6 @@ "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", "scripts/third_party/insight.ly.js"] }, - { - "matches": [ - "*://*.todoist.com/app*" - ], - "css": ["styles/common.css", "styles/font-awesome.css", "styles/bootstrap.min.css", "styles/jquery-ui.structure.min.css", "styles/jquery-ui.theme.min.css", - "styles/sites/todoist.css"], - "js": ["scripts/lib/jquery-2.1.1.min.js", "scripts/lib/jquery-ui.min.js", "scripts/lib/moment.min.js", "scripts/lib/moment-range.min.js", - "scripts/config.js", "scripts/TokenManager.js", "scripts/ApiService.js", "scripts/common.js", "scripts/TimerBase.js","scripts/TimerButton.js", - "scripts/third_party/todoist.js"] - }, { "matches": [ "*://*.wrike.com/workspace.htm*" From 7110c9bd64f2206fd482de923270b37f7fb1a1a8 Mon Sep 17 00:00:00 2001 From: chenten16 Date: Tue, 4 Oct 2022 16:05:28 +0100 Subject: [PATCH 05/10] integrating gitlab --- integrationList.json | 5 ++ scripts-2.0/style/gitlab.scss | 6 ++ scripts-2.0/style/main.scss | 1 + scripts-2.0/third_party/gitlab.js | 91 +++++++++++++++++++++++++++++++ 4 files changed, 103 insertions(+) create mode 100644 scripts-2.0/style/gitlab.scss create mode 100644 scripts-2.0/third_party/gitlab.js diff --git a/integrationList.json b/integrationList.json index 444f321..fd708a5 100644 --- a/integrationList.json +++ b/integrationList.json @@ -9,6 +9,11 @@ "script": ["todoist.js"], "isActiveInProd": true }, + { + "matches": ["*://*.gitlab.com/*"], + "script": ["gitlab.js"], + "isActiveInProd": true + }, { "matches": ["*://docs.google.com/*"], "script": ["google-docs.js"], diff --git a/scripts-2.0/style/gitlab.scss b/scripts-2.0/style/gitlab.scss new file mode 100644 index 0000000..a31c28b --- /dev/null +++ b/scripts-2.0/style/gitlab.scss @@ -0,0 +1,6 @@ +.tc-button.gitlab { + &__task-details { + margin: auto; + margin-right: 13px; + } +} \ No newline at end of file diff --git a/scripts-2.0/style/main.scss b/scripts-2.0/style/main.scss index 1d1ace1..67187d1 100644 --- a/scripts-2.0/style/main.scss +++ b/scripts-2.0/style/main.scss @@ -77,6 +77,7 @@ @import "getflow"; @import "gingkoapp"; @import "gogs"; +@import "gitlab"; @import "google-classroom"; @import "google-docs"; @import "google-inbox"; diff --git a/scripts-2.0/third_party/gitlab.js b/scripts-2.0/third_party/gitlab.js new file mode 100644 index 0000000..adaa7e8 --- /dev/null +++ b/scripts-2.0/third_party/gitlab.js @@ -0,0 +1,91 @@ +'use strict'; +const GITLAB = 'gitlab'; + +tcbutton.render( + '.issue-details .detail-page-description:not(.tc)', + { observe: false }, + function (elem) { + const prefix = [getId()].filter(Boolean) + .map(function (id) { return "#" + id; }) + .join(''); + const description = [prefix, getTitle(elem)].filter(Boolean).join(' '); + + insertButton($('.detail-page-header-actions'), description, true); + insertButton($('.time_tracker'), description); + } +); + +tcbutton.render( + '.merge-request > .detail-page-header:not(.tc)', + { observe: false }, + function (elem) { + const prefix = [getId()].filter(Boolean) + .map(function (id) { return "MR" + id + "::"; }) + .join(''); + const description = [prefix, getTitle(elem)].filter(Boolean).join(' '); + + insertButton($('.detail-page-header-actions'), description, true); + insertButton($('.time_tracker'), description); + } +); + +/** + * @param $el + * @param {String} description + * @param {boolean} prepend + */ +function insertButton($el, description, prepend = false) { + const link = tcbutton.createTimerLink({ + className: GITLAB, + additionalClasses: [GITLAB + '__task-details'], + description: description, + tags: tagsSelector, + projectName: getProjectSelector + }); + + if (prepend) { + $el.parentElement.insertBefore(link, $el); + } else { + $el.parentElement.appendChild(link, $el); + } +} + +function getTitle(parent) { + const el = parent.querySelector('.title'); + + return el ? el.textContent.trim() : ''; +} + +function getId() { + const pathname = window.location.pathname; + const pattern = /-\/(issues|merge_requests)\/(?\d+)/; + const result = pattern.test(pathname) + ? pathname.match(pattern) + : { groups: { id: '' } }; + const id = result.groups.id; + + return id; +} + +function getProjectSelector() { + const $el = $('.title .project-item-select-holder') || $('.breadcrumbs-list li:nth-last-child(3) .breadcrumb-item-text'); + return $el ? $el.textContent.trim() : ''; +} + +function tagsSelector() { + // GitLab 13.5 + const nodeList = document.querySelectorAll('div.labels span[data-qa-label-name]'); + + if (!nodeList) { + return []; + } + + const tags = []; + + for (const node of Object.values(nodeList)) { + const tagName = node.getAttribute('data-qa-label-name'); + tags.push(tagName); + } + + return tags; +} From 9650f63e35e7e2fc1413d7530ea23786568135e3 Mon Sep 17 00:00:00 2001 From: chenten16 Date: Tue, 4 Oct 2022 18:10:31 +0100 Subject: [PATCH 06/10] [FRESHDESK] integrating freshdesk --- integrationList.json | 5 +++ scripts-2.0/style/freshdesk.scss | 6 ++++ scripts-2.0/style/main.scss | 1 + scripts-2.0/third_party/freshdesk.js | 47 ++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+) create mode 100644 scripts-2.0/style/freshdesk.scss create mode 100644 scripts-2.0/third_party/freshdesk.js diff --git a/integrationList.json b/integrationList.json index fd708a5..edc2b84 100644 --- a/integrationList.json +++ b/integrationList.json @@ -14,6 +14,11 @@ "script": ["gitlab.js"], "isActiveInProd": true }, + { + "matches": ["*://*.freshdesk.com/*"], + "script": ["freshdesk.js"], + "isActiveInProd": true + }, { "matches": ["*://docs.google.com/*"], "script": ["google-docs.js"], diff --git a/scripts-2.0/style/freshdesk.scss b/scripts-2.0/style/freshdesk.scss new file mode 100644 index 0000000..cca09e9 --- /dev/null +++ b/scripts-2.0/style/freshdesk.scss @@ -0,0 +1,6 @@ +.tc-button.freshdesk { + &__task-details { + height: 100%; + margin-left: 5px; + } +} \ No newline at end of file diff --git a/scripts-2.0/style/main.scss b/scripts-2.0/style/main.scss index 67187d1..f7f5dae 100644 --- a/scripts-2.0/style/main.scss +++ b/scripts-2.0/style/main.scss @@ -71,6 +71,7 @@ @import "focuster"; @import "fogbugz"; @import "freedcamp"; +@import "freshdesk"; @import "freshrelease"; @import "freshservice"; @import "gemini"; diff --git a/scripts-2.0/third_party/freshdesk.js b/scripts-2.0/third_party/freshdesk.js new file mode 100644 index 0000000..951cbfa --- /dev/null +++ b/scripts-2.0/third_party/freshdesk.js @@ -0,0 +1,47 @@ +'use strict'; +const FRESHDESK = 'freshdesk' + +// Freshdesk mint (late 2018) +tcbutton.render('.page-actions__left:not(.tc)', { observe: true }, elem => { + if ($('.tc-button', elem)) { + return false; + } + const descriptionElem = $('.ticket-subject-heading'); + + // if there's no description element it's overview page, don't show + if (!descriptionElem) { + return false; + } + + const descriptionSelector = () => { + const ticketNumber = $('.breadcrumb__item.active').textContent.trim(); + // Remove other buttons/controls from the ticket subject + const subjectElement = $('.ticket-subject-heading').cloneNode(true); + for (const child of subjectElement.children) { + subjectElement.removeChild(child); + } + + return `${ticketNumber} ${subjectElement.textContent.trim()}`; + }; + + const link = tcbutton.createTimerLink({ + className: FRESHDESK, + additionalClasses: [FRESHDESK + '__task-details'], + description: descriptionSelector, + tags: () => { + const tagList = $('.ticket-tags ul'); + if (!tagList || + !tagList.children || + !tagList.children.length) { return []; } + + return [...tagList.querySelectorAll('li')] + .map(listItem => { + const content = listItem.querySelector('.tag-options'); + const tag = content ? content.textContent : ''; + return tag.trim().replace(/[\r\n\t]/ig, ''); /* UI has strange characters in the markup, let's avoid it */ + }); + } + }); + + elem.appendChild(link); +}); From 37a555565b5a9e7e6a4e595f823d812a82837f24 Mon Sep 17 00:00:00 2001 From: chenten16 Date: Wed, 5 Oct 2022 21:19:37 +0100 Subject: [PATCH 07/10] [AZURE] integrated! --- integrationList.json | 5 ++++ scripts-2.0/third_party/azure.js | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 scripts-2.0/third_party/azure.js diff --git a/integrationList.json b/integrationList.json index edc2b84..1e26614 100644 --- a/integrationList.json +++ b/integrationList.json @@ -9,6 +9,11 @@ "script": ["todoist.js"], "isActiveInProd": true }, + { + "matches": ["*://*.azure.com/*"], + "script": ["azure.js"], + "isActiveInProd": true + }, { "matches": ["*://*.gitlab.com/*"], "script": ["gitlab.js"], diff --git a/scripts-2.0/third_party/azure.js b/scripts-2.0/third_party/azure.js new file mode 100644 index 0000000..6e9906d --- /dev/null +++ b/scripts-2.0/third_party/azure.js @@ -0,0 +1,46 @@ +tcbutton.render('.work-item-form-headerContent:not(.tc,.flex-row)', {observe: true}, function (elem) { + if ($('.tc-button', elem)) { + return false; + } + var link, itemId, description, project, tagNames; + itemId = () => $('.work-item-form-id > span', elem).textContent; + description = () => $('.work-item-form-title input', elem).value; + project = $("input[aria-label='TimeCamp Project']") ? $("input[aria-label='TimeCamp Project']").value : $(".navigation-container .project-item .text-ellipsis").textContent; + tagNames = () => Array.from($$(".tags-items-container .tag-item:not(.tags-add-button) .tag-box")).map(e => e.innerText); + link = tcbutton.createTimerLink({ + description: () => "#" + itemId() + " " + description(), + projectName: project, + taskName: () => description(), + }); + link.style.display = "block"; + link.style.paddingTop = "0"; + link.style.paddingBottom = "0"; + link.style.marginBottom = "10px"; + link.style.cursor = 'pointer'; + link.style.width = 'fit-content'; + elem.appendChild(link); + }); + + tcbutton.render('.work-item-form-header:not(.tc,.flex-row)', {observe: true}, function (elem) { + if ($('.tc-button', elem)) { + return false; + } + var link, itemId, description, project; + itemId = () => $('.work-item-form-header > .body-xl', elem).textContent; + description = () => $('.work-item-title-textfield input', elem).value; + project = $("input[aria-label='TimeCamp Project']") ? $("input[aria-label='TimeCamp Project']").value : $(".navigation-container .project-item .text-ellipsis").textContent; + link = tcbutton.createTimerLink({ + description: () => "#" + itemId() + " " + description(), + projectName: project, + taskName: () => description() + }); + link.style.display = "block"; + link.style.paddingTop = "0"; + link.style.paddingBottom = "0"; + link.style.marginBottom = "10px"; + link.style.cursor = 'pointer'; + link.style.width = 'fit-content'; + + elem.appendChild(link); + }); + \ No newline at end of file From 3d7e7b411353f3f9eb3227e45898b42428c3a0c8 Mon Sep 17 00:00:00 2001 From: chenten16 Date: Wed, 5 Oct 2022 22:33:07 +0100 Subject: [PATCH 08/10] [ZENDESK] integrated... --- integrationList.json | 5 ++ scripts-2.0/third_party/zendesk.js | 107 +++++++++++++++++++++++++++++ 2 files changed, 112 insertions(+) create mode 100644 scripts-2.0/third_party/zendesk.js diff --git a/integrationList.json b/integrationList.json index 1e26614..0a8513a 100644 --- a/integrationList.json +++ b/integrationList.json @@ -14,6 +14,11 @@ "script": ["azure.js"], "isActiveInProd": true }, + { + "matches": ["*://*.zendesk.com/*"], + "script": ["zendesk.js"], + "isActiveInProd": true + }, { "matches": ["*://*.gitlab.com/*"], "script": ["gitlab.js"], diff --git a/scripts-2.0/third_party/zendesk.js b/scripts-2.0/third_party/zendesk.js new file mode 100644 index 0000000..bacccd1 --- /dev/null +++ b/scripts-2.0/third_party/zendesk.js @@ -0,0 +1,107 @@ +'use strict'; + +// Zendesk new UI Jul 2021 +tcbutton.render( + '.omni-conversation-pane>div>div:first-child:not(.tc)', + { observe: true }, + (elem) => { + if ($('.tc-button', elem)) { + return false; + } + const getProject = () => { + const title = document.querySelector('title'); + return title ? title.textContent.trim() : ''; + }; + + const getDescription = () => { + const ticketId = document.querySelector('header div[data-selected=true]').attributes['data-entity-id'].value || '' + + const input = elem.querySelector('input[aria-label=Subject]'); + const title = (input ? input.value : '').trim(); + + return [`#${ticketId}`, title].filter(Boolean).join(' '); + }; + + const link = tcbutton.createTimerLink({ + buttonType: 'minimal', + className: 'zendesk--2021', + description: getDescription, + projectName: getProject + }); + + elem.appendChild(link); + } +); + +// Zendesk pre-2021 +tcbutton.render( + '.pane_header:not(.tc)', + { observe: true }, + function (elem) { + if ($('.tc-button', elem)) { + return false; + } + let description; + const projectName = $('title').textContent; + + const titleFunc = function () { + const titleElem = $('.editable .ember-view input', elem); + const ticketNum = location.href.match(/tickets\/(\d+)/); + + if (titleElem !== null) { + description = titleElem.value.trim(); + } + + if (ticketNum) { + description = '#' + ticketNum[1].trim() + ' ' + description; + } + return description; + }; + + const link = tcbutton.createTimerLink({ + className: 'zendesk', + description: titleFunc, + projectName: projectName && projectName.split(' - ').shift() + }); + + // Check for strange duplicate buttons. Don't know why this happens in Zendesk. + if (elem.querySelector('.tc-button')) { + elem.removeChild(elem.querySelector('.tc-button')); + } + + elem.insertBefore(link, elem.querySelector('.btn-group')); + } +); + +const getDescription = () => { + const ticketNum = location.href.match(/tickets\/(\d+)/); + + if (!ticketNum) return null; + const id = ticketNum[1].trim(); + const titleElem = document.querySelector( + `[data-side-conversations-anchor-id="${id}"] input[aria-label="Subject"]` + ); + if (!titleElem) return null; + + return '#' + id + ' ' + titleElem.value.trim(); +}; + +tcbutton.render( + '[data-test-id="customer-context-tab-navigation"]', + { observe: true }, + function (elem) { + // Manual check for existence in this SPA. + if ($('.tc-button', elem)) { + return false; + } + // If we can't get the description on this pass, let's skip and wait for the next one + if (!getDescription()) return; + + const link = tcbutton.createTimerLink({ + className: 'zendesk-agent-ws', + description: getDescription + }); + + elem.prepend(link); + } +); \ No newline at end of file From 8037ddfe2507fdb3ee4a8a6c71e4f5c1e1de88cb Mon Sep 17 00:00:00 2001 From: chenten16 Date: Fri, 14 Oct 2022 22:59:56 +0100 Subject: [PATCH 09/10] Revert "[Asana] fixing board view button" This reverts commit 0d25173c8f0c28afbc0a44233107f34fe18256bd. --- scripts-2.0/style/asana.scss | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/scripts-2.0/style/asana.scss b/scripts-2.0/style/asana.scss index 510f08a..e1a7c98 100644 --- a/scripts-2.0/style/asana.scss +++ b/scripts-2.0/style/asana.scss @@ -23,16 +23,7 @@ } } -//board view -.BoardCard--hover{ - .tc-button.asana { - &__board-view { - opacity: 1; - } - } -} - -.BoardCardLayout.tc:hover{ +.BaseCard:hover { .tc-button.asana { &__board-view { opacity: 1; From a2392fbb074aa79ecd9c7749f18771bc8056ba17 Mon Sep 17 00:00:00 2001 From: chenten16 Date: Fri, 14 Oct 2022 23:00:22 +0100 Subject: [PATCH 10/10] Revert "[WRIKE] fixing integration with wrike" This reverts commit 28aaacaf8a05c01ccd848a6cdab0d8cb7df04d7a. --- scripts/third_party/wrike.js | 13 ++++++++++--- styles/sites/wrike.css | 14 +++----------- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/scripts/third_party/wrike.js b/scripts/third_party/wrike.js index 7268c6b..f21ee98 100644 --- a/scripts/third_party/wrike.js +++ b/scripts/third_party/wrike.js @@ -14,11 +14,18 @@ function WrikeTimer() { } var url = document.URL; - var taskOpenPattern = /&sidePanelItemId=([0-9]+)/ig; + var taskOpenPattern = /&t=([0-9]+)/ig; + var taskFromKanbanPattern = /task\/([0-9]+)/ig; var MatchRes; MatchRes = taskOpenPattern.exec(url); + console.log("timecamp", MatchRes, url, "match1"); + + if (!MatchRes) { + MatchRes = taskFromKanbanPattern.exec(url); + console.log("timecamp", MatchRes, url, "match2"); + } if (MatchRes) { var params = { v2_task_id: MatchRes[1], @@ -39,9 +46,9 @@ function WrikeTimer() { this.currentTaskName = function () { - var el = $("task-title textarea"); + var el = $("#entityname"); if (el.length) - return el.val(); + return el.html(); return false; }; diff --git a/styles/sites/wrike.css b/styles/sites/wrike.css index 34aded6..96d0f40 100644 --- a/styles/sites/wrike.css +++ b/styles/sites/wrike.css @@ -2,7 +2,7 @@ margin-left: 2px; } -#timecamp-track-button { +#timecamp-track-button { padding: 4px 18px 0 0 !important; font-size: 12px; cursor: pointer; @@ -24,12 +24,6 @@ margin-left: 5px; } -#timecamp-track-button { - display: flex; - justify-content: center; - align-items: center; -} - #tc-badge { padding: 1px 4px; height: 14px; @@ -46,10 +40,8 @@ .button-link { border: 1px solid #f0f0f0; - -webkit-border-radius: 5px; - ; - -moz-border-radius: 5px; - ; + -webkit-border-radius: 5px;; + -moz-border-radius: 5px;; border-radius: 5px; cursor: pointer; vertical-align: middle;