code stytling adjustment

This commit is contained in:
Zhigang Wang 2024-06-10 17:28:35 -07:00
parent dd90d4d4de
commit 91b0ebfea2
12 changed files with 835 additions and 258 deletions

View File

@ -104,17 +104,58 @@ p {
height: 100%; height: 100%;
margin: 0 auto; margin: 0 auto;
} }
.btn-link {text-decoration: none; color: $primary;} .btn-link {
.dropdown-menu {border: none; box-shadow: 0px 0px 24px 0px #D4D3E380; padding: 0; overflow: hidden; text-decoration: none;
.btn-link {text-decoration: none; height: 33px; width: 100%; color: #0D1637; font-size: 14px; text-align: left; border-radius: 0; color: $primary;
&:hover {background-color: #F3F6FF;}
&.active {background-color: #1748F8; color: white;}
} }
.dropdown-new {color: #1748F8; .dropdown-menu {
&:hover {background-color: white;} border: none;
box-shadow: 0px 0px 24px 0px #d4d3e380;
padding: 0;
overflow: hidden;
.btn-link {
text-decoration: none;
height: 33px;
width: 100%;
color: #0d1637;
font-size: 14px;
text-align: left;
border-radius: 0;
&:hover {
background-color: #f3f6ff;
}
&.active {
background-color: #1748f8;
color: white;
}
}
.dropdown-new {
color: #1748f8;
&:hover {
background-color: white;
}
}
.dropdown-new-input-container {
padding: 6px 12px;
position: relative;
input {
border: 1px solid #e7e8eb;
box-shadow: none;
border-radius: 3px;
padding: 0 20px 0 5px;
height: 22px;
font-size: 14px;
}
.dropdown-new-input-icon {
width: 16px;
height: 16px;
padding: 3px;
color: #3d455f;
background-color: #eeeff1;
border-radius: 3px;
position: absolute;
top: 10px;
right: 15px;
} }
.dropdown-new-input-container {padding: 6px 12px; position: relative;
input {border: 1px solid #E7E8EB; box-shadow: none; border-radius: 3px; padding: 0 20px 0 5px; height: 22px; font-size: 14px;}
.dropdown-new-input-icon {width: 16px; height: 16px; padding: 3px; color: #3D455F; background-color: #EEEFF1; border-radius: 3px; position: absolute; top: 10px; right: 15px}
} }
} }

View File

@ -2,7 +2,15 @@
<div class="freeleaps-editor"> <div class="freeleaps-editor">
<div v-if="!disabled" class="editor-control"> <div v-if="!disabled" class="editor-control">
<div v-for="(item, index) in iconList" :key="index" class="editor-item"> <div v-for="(item, index) in iconList" :key="index" class="editor-item">
<button class="item-icon" :class="{'activity': item.choose}" :data-info="item.name" @click="iconClick($event, item.type)" :data-bs-toggle="item.drop ? 'dropdown' : ''" data-bs-auto-close="outside" aria-expanded="false"> <button
class="item-icon"
:class="{ activity: item.choose }"
:data-info="item.name"
@click="iconClick($event, item.type)"
:data-bs-toggle="item.drop ? 'dropdown' : ''"
data-bs-auto-close="outside"
aria-expanded="false"
>
<svg-icon :icon="item.icon" class-name="icon" /> <svg-icon :icon="item.icon" class-name="icon" />
</button> </button>
<div class="dropmenu drop-style" v-if="item.type === 'style'"> <div class="dropmenu drop-style" v-if="item.type === 'style'">
@ -14,7 +22,9 @@
<a href="#" @click="iconClick($event, 'pre', 'style')"><pre>code</pre></a> <a href="#" @click="iconClick($event, 'pre', 'style')"><pre>code</pre></a>
</li> </li>
<li> <li>
<a href="#" @click="iconClick($event, 'blockquote', 'style')"><blockquote>引用</blockquote></a> <a href="#" @click="iconClick($event, 'blockquote', 'style')"
><blockquote>引用</blockquote></a
>
</li> </li>
<li> <li>
<a href="#" @click="iconClick($event, 'h1', 'style')"><h1>标题一</h1></a> <a href="#" @click="iconClick($event, 'h1', 'style')"><h1>标题一</h1></a>
@ -66,7 +76,14 @@
</div> </div>
</div> </div>
</div> </div>
<div class="editor-body" :contenteditable="!disabled" spellcheck="false" ref="editor" v-html="content" @blur="updateAction" /> <div
class="editor-body"
:contenteditable="!disabled"
spellcheck="false"
ref="editor"
v-html="content"
@blur="updateAction"
/>
</div> </div>
</template> </template>
<script> <script>
@ -160,7 +177,7 @@ export default {
drop: false, drop: false,
canChoose: true, canChoose: true,
choose: false choose: false
}, }
// { // {
// name: '', // name: '',
// type: 'alignjustify', // type: 'alignjustify',
@ -278,37 +295,212 @@ export default {
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.freeleaps-editor {position: relative; background-color: #fff; border-radius: 4px; border: 1px solid #a9a9a9; box-shadow: 0 1px 1px rgba(0, 0, 0, .05); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; .freeleaps-editor {
h1, h2, h3, h4, h5, h6, .h1, .h2, .h3, .h4, .h5, .h6, p, a{ font-family: inherit; font-weight: 500; line-height: 1.1; color: inherit; margin: 0; text-decoration: none; } position: relative;
code, kbd, pre, samp { font-family: Menlo, Monaco, Consolas, "Courier New", monospace; } background-color: #fff;
.editor-control { display: flex; flex-flow: row wrap; min-height: 40px; color: #333; border-bottom: 1px solid transparent; border-top-left-radius: 3px; border-top-right-radius: 3px; background-color: #f5f5f5; border-color: #ddd; } border-radius: 4px;
.dropmenu { display: none; position: absolute; top: 32px; left: 0; min-width: 160px; padding: 5px 0; margin: 2px 0 0; font-size: 14px; text-align: left; list-style: none; background-color: #fff; background-clip: padding-box; border: 1px solid #ccc; border: 1px solid rgba(0, 0, 0, .15); border-radius: 4px; box-shadow: 0 6px 12px rgba(0, 0, 0, .175); border: 1px solid #a9a9a9;
ul {padding: 10px; margin-bottom: 0;} box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
ul li { text-align: left; list-style: none; font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
a { display: block; padding: 5px 10px; white-space: nowrap; } h1,
h2,
h3,
h4,
h5,
h6,
.h1,
.h2,
.h3,
.h4,
.h5,
.h6,
p,
a {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
margin: 0;
text-decoration: none;
}
code,
kbd,
pre,
samp {
font-family: Menlo, Monaco, Consolas, 'Courier New', monospace;
}
.editor-control {
display: flex;
flex-flow: row wrap;
min-height: 40px;
color: #333;
border-bottom: 1px solid transparent;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
background-color: #f5f5f5;
border-color: #ddd;
}
.dropmenu {
display: none;
position: absolute;
top: 32px;
left: 0;
min-width: 160px;
padding: 5px 0;
margin: 2px 0 0;
font-size: 14px;
text-align: left;
list-style: none;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 4px;
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
ul {
padding: 10px;
margin-bottom: 0;
}
ul li {
text-align: left;
list-style: none;
a {
display: block;
padding: 5px 10px;
white-space: nowrap;
} }
} }
.drop-align { min-width: 100px; } }
.editor-body { height: 300px; padding: 10px; color: #000; background-color: #fff; overflow: auto; outline: none; text-align: left; border-radius: 4px; .drop-align {
p { font-size: 14px; color: #68747f; margin: 0 0 10px; } min-width: 100px;
}
.editor-body {
height: 300px;
padding: 10px;
color: #000;
background-color: #fff;
overflow: auto;
outline: none;
text-align: left;
border-radius: 4px;
p {
font-size: 14px;
color: #68747f;
margin: 0 0 10px;
}
} }
} }
.editor-item { position: relative; vertical-align: middle; border: 1px solid #ccc; cursor: pointer; line-height: 20px; border-radius: 3px; margin: 4px 0 4px 5px; background-color: white; .editor-item {
.item-icon { position: relative; display: inline-block; width: 100%; height: 100%; padding: 2px 10px; font-size: 18px; font-weight: normal; white-space: nowrap; vertical-align: middle; touch-action: manipulation; cursor: pointer; user-select: none; background-color: #fff; border: 1px solid white; outline: none; transition: all 0.1s ease-out; border-radius: 3px; position: relative;
&::after { position: absolute; top: 0; content: attr(data-info); top: 40px; left: 20px; padding: 5px 8px; border-radius: 4px; white-space: nowrap; line-height: 1.5; font-size: 13px; color: #fff; background: rgba(0,0,0,.8); -webkit-transform: translateX(-50%); transform: translateX(-50%); visibility: hidden; opacity: .9; letter-spacing: 1px; z-index: 9999; } vertical-align: middle;
&::before { position: absolute; content: ""; top: 35px; left: 20px; width: 0; height: 0; margin: 0 0 0 -6px; font-size: 0; color: rgba(0,0,0,.8); border-bottom: 6px solid currentColor; border-left: 6px solid transparent; border-right: 6px solid transparent; visibility: hidden; opacity: .9; z-index: 9999; } border: 1px solid #ccc;
&:hover, &.activity{ color: #333; background-color: #e6e6e6; border-color: #e6e6e6; } cursor: pointer;
&:hover:after, &:hover:before { visibility: visible; } line-height: 20px;
&.activity + .dropmenu { display: block; } border-radius: 3px;
margin: 4px 0 4px 5px;
background-color: white;
.item-icon {
position: relative;
display: inline-block;
width: 100%;
height: 100%;
padding: 2px 10px;
font-size: 18px;
font-weight: normal;
white-space: nowrap;
vertical-align: middle;
touch-action: manipulation;
cursor: pointer;
user-select: none;
background-color: #fff;
border: 1px solid white;
outline: none;
transition: all 0.1s ease-out;
border-radius: 3px;
&::after {
position: absolute;
top: 0;
content: attr(data-info);
top: 40px;
left: 20px;
padding: 5px 8px;
border-radius: 4px;
white-space: nowrap;
line-height: 1.5;
font-size: 13px;
color: #fff;
background: rgba(0, 0, 0, 0.8);
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
visibility: hidden;
opacity: 0.9;
letter-spacing: 1px;
z-index: 9999;
}
&::before {
position: absolute;
content: '';
top: 35px;
left: 20px;
width: 0;
height: 0;
margin: 0 0 0 -6px;
font-size: 0;
color: rgba(0, 0, 0, 0.8);
border-bottom: 6px solid currentColor;
border-left: 6px solid transparent;
border-right: 6px solid transparent;
visibility: hidden;
opacity: 0.9;
z-index: 9999;
}
&:hover,
&.activity {
color: #333;
background-color: #e6e6e6;
border-color: #e6e6e6;
}
&:hover:after,
&:hover:before {
visibility: visible;
}
&.activity + .dropmenu {
display: block;
}
} }
} }
</style> </style>
<style> <style>
.freeleaps-editor blockquote { padding: 10px 20px; font-size: 17.5px; border-left: 5px solid #f86466; background: white; } .freeleaps-editor blockquote {
.freeleaps-editor pre { display: block; padding: 9.5px; font-size: 13px; line-height: 1.42857143; color: #333; word-break: break-all; word-wrap: break-word; background-color: #f5f5f5; border: 1px solid #ccc; border-radius: 4px; } padding: 10px 20px;
.editor-body blockquote { margin-bottom: 30px; } font-size: 17.5px;
.editor-body pre { margin-bottom: 10px; } border-left: 5px solid #f86466;
.editor-body ul { padding-left: 40px; list-style-type: disc; } background: white;
.editor-body ol { padding-left: 40px; } }
.freeleaps-editor pre {
display: block;
padding: 9.5px;
font-size: 13px;
line-height: 1.42857143;
color: #333;
word-break: break-all;
word-wrap: break-word;
background-color: #f5f5f5;
border: 1px solid #ccc;
border-radius: 4px;
}
.editor-body blockquote {
margin-bottom: 30px;
}
.editor-body pre {
margin-bottom: 10px;
}
.editor-body ul {
padding-left: 40px;
list-style-type: disc;
}
.editor-body ol {
padding-left: 40px;
}
</style> </style>

View File

@ -1,6 +1,11 @@
<template> <template>
<div class="input-selector-container"> <div class="input-selector-container">
<div class="input-selector-btn" data-bs-toggle="dropdown" aria-expanded="false" id="input-selector-btn"> <div
class="input-selector-btn"
data-bs-toggle="dropdown"
aria-expanded="false"
id="input-selector-btn"
>
<span>{{ selected || '' }}</span> <span>{{ selected || '' }}</span>
<svg-icon icon="dropdown" class-name="selector-dropdown-icon" /> <svg-icon icon="dropdown" class-name="selector-dropdown-icon" />
</div> </div>
@ -8,12 +13,18 @@
<li> <li>
<button v-if="!inputing" class="btn btn-link dropdown-new" @click="newAction">+ NEW</button> <button v-if="!inputing" class="btn btn-link dropdown-new" @click="newAction">+ NEW</button>
<div v-if="inputing" class="dropdown-new-input-container"> <div v-if="inputing" class="dropdown-new-input-container">
<input type="text" v-model="newval" @keyup.enter="newItemAction"> <input type="text" v-model="newval" @keyup.enter="newItemAction" />
<svg-icon v-if="newval" icon="msg-enter" class-name="dropdown-new-input-icon" /> <svg-icon v-if="newval" icon="msg-enter" class-name="dropdown-new-input-icon" />
</div> </div>
</li> </li>
<li v-for="item in selectList" :key="item"> <li v-for="item in selectList" :key="item">
<button class="btn btn-link" @click="selectItem(item)" :class="item==selected ? 'active' : ''">{{item}}</button> <button
class="btn btn-link"
@click="selectItem(item)"
:class="item == selected ? 'active' : ''"
>
{{ item }}
</button>
</li> </li>
</ul> </ul>
</div> </div>
@ -52,7 +63,10 @@ export default {
}, },
newItemAction() { newItemAction() {
if (!this.newval) return if (!this.newval) return
this.$emit('selectedChange', {selected: this.newval, isNew: this.selectList.indexOf(this.newval) === -1}) this.$emit('selectedChange', {
selected: this.newval,
isNew: this.selectList.indexOf(this.newval) === -1
})
this.newval = '' this.newval = ''
this.inputing = false this.inputing = false
} }
@ -61,10 +75,31 @@ export default {
</script> </script>
<style lang="scss"> <style lang="scss">
.input-selector-container {width: fit-content; height: fit-content; position: relative; .input-selector-container {
.input-selector-btn {width: 153px; height: 30px; display: flex; align-items: center; overflow: hidden; cursor: pointer; border: 1px solid #E7E8EB; border-radius: 3px; padding: 0 3px 0 5px; width: fit-content;
>span {font-size: 18px; font-weight: 500; color: #242424; flex: 1; text-align: left;} height: fit-content;
.selector-dropdown-icon {color: #242424; padding: 3px;} position: relative;
.input-selector-btn {
width: 153px;
height: 30px;
display: flex;
align-items: center;
overflow: hidden;
cursor: pointer;
border: 1px solid #e7e8eb;
border-radius: 3px;
padding: 0 3px 0 5px;
> span {
font-size: 18px;
font-weight: 500;
color: #242424;
flex: 1;
text-align: left;
}
.selector-dropdown-icon {
color: #242424;
padding: 3px;
}
} }
} }
</style> </style>

View File

@ -10,7 +10,7 @@
</template> </template>
<script> <script>
import * as PDFJS from "pdfjs-dist/build/pdf" import * as PDFJS from 'pdfjs-dist/build/pdf'
import { WorksapceApi } from '@/utils/index' import { WorksapceApi } from '@/utils/index'
PDFJS.GlobalWorkerOptions.workerSrc = import('pdfjs-dist/build/pdf.worker.entry') PDFJS.GlobalWorkerOptions.workerSrc = import('pdfjs-dist/build/pdf.worker.entry')
@ -32,50 +32,56 @@ export default {
}, },
methods: { methods: {
renderPage(num) { renderPage(num) {
const canvas = document.getElementById("theCanvas"); const canvas = document.getElementById('theCanvas')
const context = canvas.getContext("2d"); const context = canvas.getContext('2d')
const scale = 1.5; const scale = 1.5
this.loadingTask.promise.then((pdf) => { this.loadingTask.promise.then((pdf) => {
pdf.getPage(num).then((page) => { pdf.getPage(num).then((page) => {
const viewport = page.getViewport({ scale }); const viewport = page.getViewport({ scale })
canvas.height = viewport.height; canvas.height = viewport.height
canvas.width = viewport.width; canvas.width = viewport.width
const renderContext = { const renderContext = {
canvasContext: context, canvasContext: context,
viewport: viewport, viewport: viewport
}; }
page.render(renderContext); page.render(renderContext)
}); })
}); })
}, },
async renderPDF() { async renderPDF() {
const response = await WorksapceApi.fetchAttachedFileAsMediaData("66673218fa83335c3b1b11ec", "6667321afa83335c3b1b11ee") const response = await WorksapceApi.fetchAttachedFileAsMediaData(
this.loadingTask = PDFJS.getDocument({url: response.data}); '66673218fa83335c3b1b11ec',
'6667321afa83335c3b1b11ee'
)
this.loadingTask = PDFJS.getDocument({ url: response.data })
this.loadingTask.promise.then((pdf) => { this.loadingTask.promise.then((pdf) => {
this.numPages = pdf.numPages; this.numPages = pdf.numPages
this.renderPage(1); this.renderPage(1)
}); })
}, },
prev() { prev() {
if (this.currentPage > 1) { if (this.currentPage > 1) {
this.currentPage--; this.currentPage--
this.renderPage(this.currentPage); this.renderPage(this.currentPage)
} }
}, },
next() { next() {
if (this.currentPage < this.numPages) { if (this.currentPage < this.numPages) {
this.currentPage++; this.currentPage++
this.renderPage(this.currentPage); this.renderPage(this.currentPage)
} }
} }
} }
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.pdf-container{position: relative; .pdf-container {
.operate{position: absolute; right: 0; top: 0; } position: relative;
.operate {
position: absolute;
right: 0;
top: 0;
}
} }
</style> </style>

View File

@ -1,23 +1,43 @@
<template> <template>
<div class="header-container"> <div class="header-container">
<div class="header-content"> <div class="header-content">
<div class="information-bar" @click="gotoMessages" :class="activePath == 'message'?'active':''"> <div
class="information-bar"
@click="gotoMessages"
:class="activePath == 'message' ? 'active' : ''"
>
<img alt="freeleaps logo" src="@/assets/message.png" /> <img alt="freeleaps logo" src="@/assets/message.png" />
</div> </div>
<div class="navigation-container" role="navigation"> <div class="navigation-container" role="navigation">
<button class="navigation-item" @click="gotoWorkspace" :class="activePath == 'Workspace'?'active':''"> <button
class="navigation-item"
@click="gotoWorkspace"
:class="activePath == 'Workspace' ? 'active' : ''"
>
<svg-icon icon="workspace" class-name="icon" /> <svg-icon icon="workspace" class-name="icon" />
Workspace Workspace
</button> </button>
<button class="navigation-item" @click="gotoRequests" :class="activePath == 'Requests'?'active':''"> <button
class="navigation-item"
@click="gotoRequests"
:class="activePath == 'Requests' ? 'active' : ''"
>
<svg-icon icon="requests" class-name="icon" /> <svg-icon icon="requests" class-name="icon" />
Requests Requests
</button> </button>
<button class="navigation-item" @click="gotoProviders" :class="activePath == 'Providers'?'active':''"> <button
class="navigation-item"
@click="gotoProviders"
:class="activePath == 'Providers' ? 'active' : ''"
>
<svg-icon icon="providers" class-name="icon" /> <svg-icon icon="providers" class-name="icon" />
Providers Providers
</button> </button>
<button class="navigation-item" @click="gotoIssueRequest" :class="activePath == 'Post'?'active':''"> <button
class="navigation-item"
@click="gotoIssueRequest"
:class="activePath == 'Post' ? 'active' : ''"
>
<svg-icon icon="post" class-name="icon" /> <svg-icon icon="post" class-name="icon" />
Post Post
</button> </button>
@ -163,7 +183,9 @@ export default {
top: 0; top: 0;
background-color: #f44837; background-color: #f44837;
} }
&.active {border: 1px solid $primary;} &.active {
border: 1px solid $primary;
}
} }
.profile-container { .profile-container {

View File

@ -56,9 +56,7 @@ export default {
return return
} }
this.message = applicantValidator.emailValidator.validate( this.message = applicantValidator.emailValidator.validate(this.email)
this.email
);
if (this.message != null) return if (this.message != null) return

View File

@ -2,7 +2,12 @@
<div class="message-container"> <div class="message-container">
<div class="message-hub-conainter"> <div class="message-hub-conainter">
<div v-if="conversations && conversations.length > 0" class="conversation-list-container"> <div v-if="conversations && conversations.length > 0" class="conversation-list-container">
<div v-for="(conversation, index) in conversations" :key="index" class="conversation-container" @click="selectConversation(conversation)"> <div
v-for="(conversation, index) in conversations"
:key="index"
class="conversation-container"
@click="selectConversation(conversation)"
>
<img class="participant-portrait" alt="user portrait" src="@/assets/profile.png" /> <img class="participant-portrait" alt="user portrait" src="@/assets/profile.png" />
<div class="conversation-summary-container"> <div class="conversation-summary-container">
<div class="conversation-summary-header-container"> <div class="conversation-summary-header-container">
@ -10,33 +15,58 @@
{{ conversation.summary.contact.first_name }} {{ conversation.summary.contact.first_name }}
{{ conversation.summary.contact.last_name }} {{ conversation.summary.contact.last_name }}
</span> </span>
<span class="conversation-last-update-date">{{ getDateFromFulltimeString(conversation.summary.last_message.create_time) }}</span> <span class="conversation-last-update-date">{{
getDateFromFulltimeString(conversation.summary.last_message.create_time)
}}</span>
</div> </div>
<div class="conversation-summary-highlight-container">{{ conversation.summary.last_message.message_body }}</div> <div class="conversation-summary-highlight-container">
{{ conversation.summary.last_message.message_body }}
</div> </div>
</div> </div>
</div> </div>
<div v-if="!conversations || conversations.length==0" class="conversation-list-empty-container"> </div>
<div
v-if="!conversations || conversations.length == 0"
class="conversation-list-empty-container"
>
Empty conversation Empty conversation
</div> </div>
<div class="message-panel-container"> <div class="message-panel-container">
<div v-if="current_thread" class="message-thread-container"> <div v-if="current_thread" class="message-thread-container">
<div v-for="(item, index) in current_thread.conversation.messages" :key="index" class="message-item-container" :class="item.sender_profile.me ? 'me': ''"> <div
v-for="(item, index) in current_thread.conversation.messages"
:key="index"
class="message-item-container"
:class="item.sender_profile.me ? 'me' : ''"
>
<div class="message-item-header-container"> <div class="message-item-header-container">
<img class="message-item-sender-portrait" alt="user portrait" src="@/assets/profile.png" /> <img
class="message-item-sender-portrait"
alt="user portrait"
src="@/assets/profile.png"
/>
<span class="message-item-sender-fullname"> <span class="message-item-sender-fullname">
{{ item.sender_profile.first_name }} {{ item.sender_profile.first_name }}
{{ item.sender_profile.last_name }} {{ item.sender_profile.last_name }}
</span> </span>
<span class="message-item-create-time"> {{ getDateFromFulltimeString(item.raw_data.create_time) }}</span> <span class="message-item-create-time">
{{ getDateFromFulltimeString(item.raw_data.create_time) }}</span
>
</div> </div>
<div class="message-item-message-body">{{ item.raw_data.message_body }}</div> <div class="message-item-message-body">{{ item.raw_data.message_body }}</div>
</div> </div>
</div> </div>
<div v-if="!current_thread" class="message-thread-empty-container">Please choose conversation</div> <div v-if="!current_thread" class="message-thread-empty-container">
Please choose conversation
</div>
<div class="message-writing-panel-container"> <div class="message-writing-panel-container">
<svg-icon icon="msg-enter" class-name="writing-message-enter" /> <svg-icon icon="msg-enter" class-name="writing-message-enter" />
<input class="writing-message-input" type="text" v-model="writtenMessage" @keypress.enter="sendMessage(current_thread.conversation.information.conversation_id)" /> <input
class="writing-message-input"
type="text"
v-model="writtenMessage"
@keypress.enter="sendMessage(current_thread.conversation.information.conversation_id)"
/>
</div> </div>
</div> </div>
</div> </div>
@ -101,43 +131,196 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.message-container { display: flex; align-items: center; width: 100%; height: $body-height; padding: 24px 24px 0 24px; justify-content: center; .message-container {
.message-hub-conainter { display: flex; box-shadow: 0px 0px 24px 0px #D4D3E380; border-radius: 12px; width: 100%; max-width: $body-width; height: 100%; overflow: hidden; display: flex;
.conversation-list-container {height: 100%; overflow: auto; width: 300px; align-items: center;
.conversation-container {background-color: #F3F3F5; display: flex; cursor: pointer; height: 98px; padding: 18px; width: 100%;
.participant-portrait {width: 36px; height: 36px; border-radius: 18px; margin-right: 16px;} height: $body-height;
.conversation-summary-container {display: flex; flex-direction: column; flex: 1; overflow: hidden; padding: 24px 24px 0 24px;
.conversation-summary-header-container {display: flex; align-items: center; height: 22px; color: #0D1637; justify-content: center;
.participant-fullname {font-weight: 500; flex: 1; white-space: nowrap; text-overflow: ellipsis; overflow: hidden;} .message-hub-conainter {
.conversation-last-update-date {white-space: nowrap; flex-shrink: 0;} display: flex;
box-shadow: 0px 0px 24px 0px #d4d3e380;
border-radius: 12px;
width: 100%;
max-width: $body-width;
height: 100%;
overflow: hidden;
.conversation-list-container {
height: 100%;
overflow: auto;
width: 300px;
.conversation-container {
background-color: #f3f3f5;
display: flex;
cursor: pointer;
height: 98px;
padding: 18px;
.participant-portrait {
width: 36px;
height: 36px;
border-radius: 18px;
margin-right: 16px;
} }
.conversation-summary-highlight-container {font-size: 14px; color: #6E7387; white-space: normal; overflow: hidden; flex: 1; text-align: left; word-break: break-word} .conversation-summary-container {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
.conversation-summary-header-container {
display: flex;
align-items: center;
height: 22px;
color: #0d1637;
.participant-fullname {
font-weight: 500;
flex: 1;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
} }
&:hover {opacity: .78;} .conversation-last-update-date {
&.selected {background-color: transparent;} white-space: nowrap;
flex-shrink: 0;
} }
} }
.conversation-list-empty-container {height: 100%; overflow: auto; width: 300px; background-color: #F3F3F5; display: flex; align-items: center; justify-content: center;} .conversation-summary-highlight-container {
.message-panel-container {height: 100%; overflow: auto; flex: 1; padding-bottom: 98px; position: relative; font-size: 14px;
.message-thread-container {height: 100%; overflow: auto; padding: 20px 32px; display: flex; flex-direction: column; color: #6e7387;
.message-item-container {max-width: 70%; margin-bottom: 15px; width: fit-content; display: flex; flex-direction: column; white-space: normal;
.message-item-header-container {display: flex; align-items: center; margin-bottom: 6px; width: 100%; overflow: hidden; font-size: 18px; width: fit-content; max-width: 100%; overflow: hidden;
.message-item-sender-portrait {width: 36px; height: 36px; border-radius: 18px; } flex: 1;
.message-item-sender-fullname {overflow: hidden; white-space: nowrap; text-overflow: ellipsis; color: #16181E; font-weight: 500; margin: 0 7px; } text-align: left;
.message-item-create-time {color: #737478; white-space: nowrap; flex-shrink: 0; font-size: 16px; } word-break: break-word;
}
}
&:hover {
opacity: 0.78;
}
&.selected {
background-color: transparent;
}
}
}
.conversation-list-empty-container {
height: 100%;
overflow: auto;
width: 300px;
background-color: #f3f3f5;
display: flex;
align-items: center;
justify-content: center;
}
.message-panel-container {
height: 100%;
overflow: auto;
flex: 1;
padding-bottom: 98px;
position: relative;
.message-thread-container {
height: 100%;
overflow: auto;
padding: 20px 32px;
display: flex;
flex-direction: column;
.message-item-container {
max-width: 70%;
margin-bottom: 15px;
width: fit-content;
display: flex;
flex-direction: column;
.message-item-header-container {
display: flex;
align-items: center;
margin-bottom: 6px;
width: 100%;
overflow: hidden;
font-size: 18px;
width: fit-content;
max-width: 100%;
.message-item-sender-portrait {
width: 36px;
height: 36px;
border-radius: 18px;
}
.message-item-sender-fullname {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
color: #16181e;
font-weight: 500;
margin: 0 7px;
}
.message-item-create-time {
color: #737478;
white-space: nowrap;
flex-shrink: 0;
font-size: 16px;
}
}
.message-item-message-body {
font-size: 14px;
line-height: 17px;
color: #0d1637;
background-color: #f3f3f5;
padding: 12px;
border-radius: 12px;
overflow: hidden;
width: fit-content;
max-width: 100%;
text-align: left;
word-break: break-word;
} }
.message-item-message-body {font-size: 14px; line-height: 17px; color: #0D1637; background-color: #F3F3F5; padding: 12px; border-radius: 12px; overflow: hidden; width: fit-content; max-width: 100%; text-align: left; word-break: break-word}
&.me {align-self: flex-end; &.me {
.message-item-header-container {flex-direction: row-reverse; align-self: flex-end;} align-self: flex-end;
.message-item-message-body {background-color: $primary; color: white; align-self: flex-end;} .message-item-header-container {
flex-direction: row-reverse;
align-self: flex-end;
}
.message-item-message-body {
background-color: $primary;
color: white;
align-self: flex-end;
} }
} }
} }
.message-thread-empty-container {height: 100%; overflow: auto; padding: 20px 32px; display: flex; align-items: center; justify-content: center;} }
.message-writing-panel-container {height: 98px; padding: 24px; background-color: white; position: absolute; bottom: 0; left: 0; width: 100%; .message-thread-empty-container {
.writing-message-enter {position: absolute; height: 24px; width: 24px; background-color: #F3F3F5; padding: 7px; color: #9EA2AF; top: 37px; right: 37px; border-radius: 7px;} height: 100%;
.writing-message-input {height: 50px; border: 1px solid #E1E1E1; box-shadow: none; width: 100%; padding: 0 48px 0 12px; border-radius: 12px;} overflow: auto;
padding: 20px 32px;
display: flex;
align-items: center;
justify-content: center;
}
.message-writing-panel-container {
height: 98px;
padding: 24px;
background-color: white;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
.writing-message-enter {
position: absolute;
height: 24px;
width: 24px;
background-color: #f3f3f5;
padding: 7px;
color: #9ea2af;
top: 37px;
right: 37px;
border-radius: 7px;
}
.writing-message-input {
height: 50px;
border: 1px solid #e1e1e1;
box-shadow: none;
width: 100%;
padding: 0 48px 0 12px;
border-radius: 12px;
}
} }
} }
} }

View File

@ -1,22 +1,43 @@
<template> <template>
<div class="request-hub"> <div class="request-hub">
<div v-for="(group, index) in requestGroups" :key="index" class="request-invitations" :id="group.name"> <div
v-for="(group, index) in requestGroups"
:key="index"
class="request-invitations"
:id="group.name"
>
<div v-if="group.data" class="accordion" id="request-invitation-container"> <div v-if="group.data" class="accordion" id="request-invitation-container">
<div v-for="(request, index) in group.data" :key="index" :id="request.id" class="accordion-item my-3"> <div
v-for="(request, index) in group.data"
:key="index"
:id="request.id"
class="accordion-item my-3"
>
<h2 class="accordion-header"> <h2 class="accordion-header">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse" <button
:data-bs-target="'#collapse' + index" aria-expanded="false" :aria-controls="'collapse' + index"> class="accordion-button collapsed"
type="button"
data-bs-toggle="collapse"
:data-bs-target="'#collapse' + index"
aria-expanded="false"
:aria-controls="'collapse' + index"
>
<div class="request-content-container"> <div class="request-content-container">
<div class="request-content-issuer-container"> <div class="request-content-issuer-container">
<label class="request-content-label" for="request-content-issuer-box">Issuer</label> <label class="request-content-label" for="request-content-issuer-box"
>Issuer</label
>
<div class="request-content-box" id="request-content-issuer-box"> <div class="request-content-box" id="request-content-issuer-box">
<span class="request-content-issuer-text"> <span class="request-content-issuer-text">
{{ request.issuer_profile.first_name }} {{ request.issuer_profile.first_name }}
{{ request.issuer_profile.last_name }}</span> {{ request.issuer_profile.last_name }}</span
>
</div> </div>
</div> </div>
<div class="request-content-title-container"> <div class="request-content-title-container">
<label class="request-content-label" for="request-content-title-box">Request</label> <label class="request-content-label" for="request-content-title-box"
>Request</label
>
<div class="request-content-box" id="request-content-title-box"> <div class="request-content-box" id="request-content-title-box">
<span class="request-content-title-text"> {{ request.title }}</span> <span class="request-content-title-text"> {{ request.title }}</span>
</div> </div>
@ -40,8 +61,11 @@
</div> </div>
</button> </button>
</h2> </h2>
<div :id="'collapse' + index" class="accordion-collapse collapse" <div
data-bs-parent="#request-invitation-container"> :id="'collapse' + index"
class="accordion-collapse collapse"
data-bs-parent="#request-invitation-container"
>
<div class="accordion-body"> <div class="accordion-body">
<div class="request-description-container"> <div class="request-description-container">
<button class="make-proposal-button" type="button" @click="Propose(request)"> <button class="make-proposal-button" type="button" @click="Propose(request)">
@ -49,51 +73,95 @@
</button> </button>
<div class="request-description-content" v-html="request.content"></div> <div class="request-description-content" v-html="request.content"></div>
<div v-for="(file, index) in request.attached_files" :key="index"> <div v-for="(file, index) in request.attached_files" :key="index">
<button @click="previewAttachedFile(request.id, file.document_id)">Preview{{ file.file_name }}</button> <button @click="previewAttachedFile(request.id, file.document_id)">
<button @click="downloadAttachedFile(request.id, file.document_id)">Download{{ file.file_name }}</button> Preview{{ file.file_name }}
</button>
<button @click="downloadAttachedFile(request.id, file.document_id)">
Download{{ file.file_name }}
</button>
</div> </div>
</div> </div>
<div class="issuer-profile-container"> <div class="issuer-profile-container">
<label class="issuer-profile-label" for="issuer-achievement-container">Issuer profile</label> <label class="issuer-profile-label" for="issuer-achievement-container"
>Issuer profile</label
>
<div class="issuer-achievement-container" id="issuer-achievement-container"> <div class="issuer-achievement-container" id="issuer-achievement-container">
<div class="issuer-achievement-isssuer-container"> <div class="issuer-achievement-isssuer-container">
<label class="issuer-achievement-label" for="issuer-achievement-isssuer-content-div">Name</label> <label
<div class="issuer-achievement-content-container" id="issuer-achievement-isssuer-content-div"> class="issuer-achievement-label"
for="issuer-achievement-isssuer-content-div"
>Name</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-isssuer-content-div"
>
<span class="issuer-achievement-issuer-text"> <span class="issuer-achievement-issuer-text">
{{ request.issuer_profile.first_name }} {{ request.issuer_profile.first_name }}
{{ request.issuer_profile.last_name }}</span> {{ request.issuer_profile.last_name }}</span
>
</div> </div>
</div> </div>
<div class="issuer-achievement-stay-container"> <div class="issuer-achievement-stay-container">
<label class="issuer-achievement-label" for="issuer-achievement-stay-content-div">Stay on <label
Freeleaps</label> class="issuer-achievement-label"
<div class="issuer-achievement-content-container" id="issuer-achievement-stay-content-div"> for="issuer-achievement-stay-content-div"
>Stay on Freeleaps</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-stay-content-div"
>
<span class="issuer-achievement-stay-content-text"> <span class="issuer-achievement-stay-content-text">
{{ request.issuer_achievement.activeness.days_of_staying_on }} day(s)</span> {{ request.issuer_achievement.activeness.days_of_staying_on }} day(s)</span
>
</div> </div>
</div> </div>
<div class="issuer-achievement-paid-container"> <div class="issuer-achievement-paid-container">
<label class="issuer-achievement-label" for="issuer-achievement-paid-content-div">Total <label
payment</label> class="issuer-achievement-label"
<div class="issuer-achievement-content-container" id="issuer-achievement-stay-content-div"> for="issuer-achievement-paid-content-div"
>Total payment</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-stay-content-div"
>
<span class="issuer-achievement-paid-content-text"> <span class="issuer-achievement-paid-content-text">
{{ request.issuer_achievement.issuer.spending.total }} {{ request.issuer_achievement.issuer.spending.total }}
{{ request.issuer_achievement.issuer.spending.currency }}</span> {{ request.issuer_achievement.issuer.spending.currency }}</span
>
</div> </div>
</div> </div>
<div class="issuer-achievement-deposit-container"> <div class="issuer-achievement-deposit-container">
<label class="issuer-achievement-label" for="issuer-achievement-deposit-content-div">Deposit</label> <label
<div class="issuer-achievement-content-container" id="issuer-achievement-deposit-content-div"> class="issuer-achievement-label"
for="issuer-achievement-deposit-content-div"
>Deposit</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-deposit-content-div"
>
<span class="issuer-achievement-deposit-content-text"> <span class="issuer-achievement-deposit-content-text">
{{ request.issuer_achievement.issuer.deposit.available }} {{ request.issuer_achievement.issuer.deposit.available }}
{{ request.issuer_achievement.issuer.deposit.currency }}</span> {{ request.issuer_achievement.issuer.deposit.currency }}</span
>
</div> </div>
</div> </div>
<div class="issuer-achievement-credit-container"> <div class="issuer-achievement-credit-container">
<label class="issuer-achievement-label" for="issuer-achievement-credit-content-div">Credit</label> <label
<div class="issuer-achievement-content-container" id="issuer-achievement-credit-content-div"> class="issuer-achievement-label"
for="issuer-achievement-credit-content-div"
>Credit</label
>
<div
class="issuer-achievement-content-container"
id="issuer-achievement-credit-content-div"
>
<span class="issuer-achievement-credit-content-text"> <span class="issuer-achievement-credit-content-text">
{{ request.issuer_achievement.activeness.credit }}</span> {{ request.issuer_achievement.activeness.credit }}</span
>
</div> </div>
</div> </div>
</div> </div>
@ -140,36 +208,36 @@ export default {
return DateUtils.FromJsonToDateString(fulltime) return DateUtils.FromJsonToDateString(fulltime)
}, },
previewAttachedFile(request_id, document_id) { previewAttachedFile(request_id, document_id) {
WorksapceApi.fetchAttachedFileAsMediaData(request_id, document_id).then(response => { WorksapceApi.fetchAttachedFileAsMediaData(request_id, document_id)
.then((response) => {
let media_data = response.data let media_data = response.data
console.log(media_data) console.log(media_data)
//TODO: navigate to the preview page //TODO: navigate to the preview page
}).catch((error) => { })
.catch((error) => {
this.mnx_backendErrorHandler(error) this.mnx_backendErrorHandler(error)
} })
)
}, },
downloadAttachedFile(request_id, document_id) { downloadAttachedFile(request_id, document_id) {
WorksapceApi.fetchAttachedFileAsDownload(request_id, document_id).then(response => { WorksapceApi.fetchAttachedFileAsDownload(request_id, document_id)
.then((response) => {
// create file link in browser's memory // create file link in browser's memory
const href = URL.createObjectURL(response.data); const href = URL.createObjectURL(response.data)
// create "a" HTML element with href to file & click // create "a" HTML element with href to file & click
const link = document.createElement('a'); const link = document.createElement('a')
link.href = href; link.href = href
link.setAttribute('download', 'file.pdf'); //or any other extension link.setAttribute('download', 'file.pdf') //or any other extension
document.body.appendChild(link); document.body.appendChild(link)
link.click(); link.click()
// clean up "a" element & remove ObjectURL // clean up "a" element & remove ObjectURL
document.body.removeChild(link); document.body.removeChild(link)
URL.revokeObjectURL(href); //TODO: navigate to the preview page URL.revokeObjectURL(href) //TODO: navigate to the preview page
}).catch((error) => { })
.catch((error) => {
this.mnx_backendErrorHandler(error) this.mnx_backendErrorHandler(error)
} })
)
} }
} }
} }

View File

@ -911,24 +911,48 @@ export default {
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.workspace-container {width: 100%; display: flex; align-items: center; justify-content: center} .workspace-container {
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.workspace-header { .workspace-header {
@extend .flex-row-container; @extend .flex-row-container;
@extend .justify-content-end; @extend .justify-content-end;
} }
.workspace-body {width: 100%; max-width: $body-width; padding: 24px 0; .workspace-body {
.accordion {box-shadow: 0px 0px 24px 0px #D4D3E380; border: none; border-radius: 12px; margin-bottom: 16px; width: 100%;
.accordion-item {border: none; max-width: $body-width;
.accordion-header {border: none; padding: 24px 0;
.accordion-button {box-shadow: none; padding: 12px} .accordion {
box-shadow: 0px 0px 24px 0px #d4d3e380;
border: none;
border-radius: 12px;
margin-bottom: 16px;
.accordion-item {
border: none;
.accordion-header {
border: none;
.accordion-button {
box-shadow: none;
padding: 12px;
}
} }
} }
} }
} }
.workspace-item-bar {display: flex; align-items: center; margin: 0 24px 0 0; border: 1px dashed #AEBFFD; border-radius: 3px; flex: 1} .workspace-item-bar {
display: flex;
align-items: center;
margin: 0 24px 0 0;
border: 1px dashed #aebffd;
border-radius: 3px;
flex: 1;
}
.workspace-item-bar-left { .workspace-item-bar-left {
@extend .flex-row-container; @extend .flex-row-container;

View File

@ -105,14 +105,23 @@
</div> </div>
</div> </div>
<div class="action-bar"> <div class="action-bar">
<input-selector :select-list="products" :selected="selectedProduct" @selectedChange="selectedChange" /> <input-selector
:select-list="products"
:selected="selectedProduct"
@selectedChange="selectedChange"
/>
<div class="upload-contianer"> <div class="upload-contianer">
<label class="btn btn-link"> <label class="btn btn-link">
<span v-if="!uploadFile">Upload file</span> <span v-if="!uploadFile">Upload file</span>
<span v-if="uploadFile">{{ uploadFile.name }}</span> <span v-if="uploadFile">{{ uploadFile.name }}</span>
<input type="file" hidden @change="handleFileUpload" /> <input type="file" hidden @change="handleFileUpload" />
</label> </label>
<svg-icon v-if="uploadFile" icon="delete" class-name="delete-icon" @click.stop="clearFile" /> <svg-icon
v-if="uploadFile"
icon="delete"
class-name="delete-icon"
@click.stop="clearFile"
/>
</div> </div>
<div class="flex-1" /> <div class="flex-1" />
<button <button
@ -208,7 +217,7 @@ export default {
initProducts() { initProducts() {
WorksapceApi.fetchProducts() WorksapceApi.fetchProducts()
.then((response) => { .then((response) => {
this.products = (response.data || []).map(p => p.name) this.products = (response.data || []).map((p) => p.name)
}) })
.catch((error) => { .catch((error) => {
this.mnx_backendErrorHandler(error) this.mnx_backendErrorHandler(error)
@ -325,9 +334,16 @@ export default {
} }
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.upload-contianer {display: flex; align-items: center;} .upload-contianer {
.delete-icon {color: $primary; cursor: pointer; display: flex;
&:hover {opacity: .78;} align-items: center;
}
.delete-icon {
color: $primary;
cursor: pointer;
&:hover {
opacity: 0.78;
}
} }
.request-issue-container { .request-issue-container {
@extend .flex-colum-container; @extend .flex-colum-container;

View File

@ -311,10 +311,7 @@ class WorksapceApi {
formData.append('request_id', request_id) formData.append('request_id', request_id)
formData.append('file', file) formData.append('file', file)
const request = backendAxios.post( const request = backendAxios.post('/api/workspace/request/attach-file-for-request', formData, {
'/api/workspace/request/attach-file-for-request',
formData,
{
headers: { headers: {
'Content-Type': 'multipart/form-data', 'Content-Type': 'multipart/form-data',
Authorization: `Bearer ${jwt}` Authorization: `Bearer ${jwt}`
@ -335,23 +332,18 @@ class WorksapceApi {
} }
) )
return request return request
} }
static fetchAttachedFileAsDownload(request_id, document_id) { static fetchAttachedFileAsDownload(request_id, document_id) {
let jwt = userUtils.getJwtToken() let jwt = userUtils.getJwtToken()
const request = backendAxios.get( const request = backendAxios.get('/api/workspace/request/fetch-attached-file-as-download', {
'/api/workspace/request/fetch-attached-file-as-download',
{
params: { params: {
request_id: request_id, request_id: request_id,
document_id: document_id, document_id: document_id
}, },
responseType: 'blob', responseType: 'blob',
headers: { Authorization: `Bearer ${jwt}` } headers: { Authorization: `Bearer ${jwt}` }
} })
)
return request return request
} }
} }