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%;
margin: 0 auto;
}
.btn-link {text-decoration: none; color: $primary;}
.dropdown-menu {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;}
.btn-link {
text-decoration: none;
color: $primary;
}
.dropdown-menu {
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 {
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 v-if="!disabled" class="editor-control">
<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" />
</button>
<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>
</li>
<li>
<a href="#" @click="iconClick($event, 'blockquote', 'style')"><blockquote>引用</blockquote></a>
<a href="#" @click="iconClick($event, 'blockquote', 'style')"
><blockquote>引用</blockquote></a
>
</li>
<li>
<a href="#" @click="iconClick($event, 'h1', 'style')"><h1>标题一</h1></a>
@ -23,13 +33,13 @@
<a href="#" @click="iconClick($event, 'h2', 'style')"><h2>标题二</h2></a>
</li>
<li>
<a href="#" @click="iconClick($event, 'h3', 'style')"><h3>标题三 </h3></a>
<a href="#" @click="iconClick($event, 'h3', 'style')"><h3>标题三</h3></a>
</li>
<li>
<a href="#" @click="iconClick($event, 'h4', 'style')"><h4>标题四 </h4></a>
<a href="#" @click="iconClick($event, 'h4', 'style')"><h4>标题四</h4></a>
</li>
<li>
<a href="#" @click="iconClick($event, 'h5', 'style')"><h5>标题五 </h5></a>
<a href="#" @click="iconClick($event, 'h5', 'style')"><h5>标题五</h5></a>
</li>
<li>
<a href="#" @click="iconClick($event, 'h6', 'style')"><h6>标题六</h6></a>
@ -66,7 +76,14 @@
</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>
</template>
<script>
@ -160,7 +177,7 @@ export default {
drop: false,
canChoose: true,
choose: false
},
}
// {
// name: '',
// type: 'alignjustify',
@ -173,7 +190,7 @@ export default {
}
},
methods: {
iconClick (event, type, dropType) {
iconClick(event, type, dropType) {
event.preventDefault()
this.$refs.editor.focus()
this.selectedRange = this.getSelect()
@ -278,37 +295,212 @@ export default {
</script>
<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;
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, .15); border-radius: 4px; box-shadow: 0 6px 12px rgba(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; }
.freeleaps-editor {
position: relative;
background-color: #fff;
border-radius: 4px;
border: 1px solid #a9a9a9;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.05);
font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif;
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;
p { font-size: 14px; color: #68747f; margin: 0 0 10px; }
.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;
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;
.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,.8); -webkit-transform: translateX(-50%); transform: translateX(-50%); visibility: hidden; opacity: .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,.8); border-bottom: 6px solid currentColor; border-left: 6px solid transparent; border-right: 6px solid transparent; visibility: hidden; opacity: .9; z-index: 9999; }
&:hover, &.activity{ color: #333; background-color: #e6e6e6; border-color: #e6e6e6; }
&:hover:after, &:hover:before { visibility: visible; }
&.activity + .dropmenu { display: block; }
.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;
.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>
.freeleaps-editor blockquote { padding: 10px 20px; font-size: 17.5px; border-left: 5px solid #f86466; background: white; }
.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>
.freeleaps-editor blockquote {
padding: 10px 20px;
font-size: 17.5px;
border-left: 5px solid #f86466;
background: white;
}
.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>

View File

@ -1,20 +1,31 @@
<template>
<div class="input-selector-container">
<div class="input-selector-btn" data-bs-toggle="dropdown" aria-expanded="false" id="input-selector-btn">
<span>{{selected || ''}}</span>
<svg-icon icon="dropdown" class-name="selector-dropdown-icon" />
<div
class="input-selector-btn"
data-bs-toggle="dropdown"
aria-expanded="false"
id="input-selector-btn"
>
<span>{{ selected || '' }}</span>
<svg-icon icon="dropdown" class-name="selector-dropdown-icon" />
</div>
<ul class="dropdown-menu" aria-labelledby="input-selector-btn">
<li>
<button v-if="!inputing" class="btn btn-link dropdown-new" @click="newAction">+ NEW</button>
<div v-if="inputing" class="dropdown-new-input-container">
<input type="text" v-model="newval" @keyup.enter="newItemAction">
<svg-icon v-if="newval" icon="msg-enter" class-name="dropdown-new-input-icon" />
</div>
</li>
<li v-for="item in selectList" :key="item">
<button class="btn btn-link" @click="selectItem(item)" :class="item==selected ? 'active' : ''">{{item}}</button>
</li>
<li>
<button v-if="!inputing" class="btn btn-link dropdown-new" @click="newAction">+ NEW</button>
<div v-if="inputing" class="dropdown-new-input-container">
<input type="text" v-model="newval" @keyup.enter="newItemAction" />
<svg-icon v-if="newval" icon="msg-enter" class-name="dropdown-new-input-icon" />
</div>
</li>
<li v-for="item in selectList" :key="item">
<button
class="btn btn-link"
@click="selectItem(item)"
:class="item == selected ? 'active' : ''"
>
{{ item }}
</button>
</li>
</ul>
</div>
</template>
@ -25,46 +36,70 @@ export default {
name: 'InputSelector',
props: {
selectList: {
type: Array,
default: () => []
type: Array,
default: () => []
},
selected: {
type: String,
default: ''
type: String,
default: ''
}
},
data() {
return {
inputing: true,
newval: ''
inputing: true,
newval: ''
}
},
mounted() { },
components: {SvgIcon},
mounted() {},
components: { SvgIcon },
methods: {
newAction($event) {
$event.stopPropagation()
this.newval = ''
this.inputing = true
$event.stopPropagation()
this.newval = ''
this.inputing = true
},
selectItem(item) {
this.$emit('selectedChange', {selected: item, isNew: false})
this.$emit('selectedChange', { selected: item, isNew: false })
},
newItemAction() {
if (!this.newval) return
this.$emit('selectedChange', {selected: this.newval, isNew: this.selectList.indexOf(this.newval) === -1})
this.newval = ''
this.inputing = false
if (!this.newval) return
this.$emit('selectedChange', {
selected: this.newval,
isNew: this.selectList.indexOf(this.newval) === -1
})
this.newval = ''
this.inputing = false
}
}
}
</script>
<style lang="scss">
.input-selector-container {width: fit-content; height: fit-content; 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;}
.input-selector-container {
width: fit-content;
height: fit-content;
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>

View File

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

View File

@ -1,23 +1,43 @@
<template>
<div class="header-container">
<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" />
</div>
<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" />
Workspace
</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" />
Requests
</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" />
Providers
</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" />
Post
</button>
@ -163,7 +183,9 @@ export default {
top: 0;
background-color: #f44837;
}
&.active {border: 1px solid $primary;}
&.active {
border: 1px solid $primary;
}
}
.profile-container {

View File

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

View File

@ -52,4 +52,4 @@ export default {
</script>
<style lang="scss" scoped>
// .pdf-viewer {height: $body-height;}
</style>
</style>

View File

@ -1,8 +1,13 @@
<template>
<div class="message-container">
<div class="message-hub-conainter">
<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-if="conversations && conversations.length > 0" class="conversation-list-container">
<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" />
<div class="conversation-summary-container">
<div class="conversation-summary-header-container">
@ -10,33 +15,58 @@
{{ conversation.summary.contact.first_name }}
{{ conversation.summary.contact.last_name }}
</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 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 v-if="!conversations || conversations.length==0" class="conversation-list-empty-container">
<div
v-if="!conversations || conversations.length == 0"
class="conversation-list-empty-container"
>
Empty conversation
</div>
<div class="message-panel-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">
<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">
{{ item.sender_profile.first_name }}
{{ item.sender_profile.last_name }}
</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 class="message-item-message-body">{{ item.raw_data.message_body }}</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">
<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>
@ -101,43 +131,196 @@ export default {
}
</script>
<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-hub-conainter { 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-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;}
.conversation-last-update-date {white-space: nowrap; flex-shrink: 0;}
}
.conversation-summary-highlight-container {font-size: 14px; color: #6E7387; white-space: normal; overflow: hidden; flex: 1; text-align: left; word-break: break-word}
.message-container {
display: flex;
align-items: center;
width: 100%;
height: $body-height;
padding: 24px 24px 0 24px;
justify-content: center;
.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;
.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-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;
}
.conversation-last-update-date {
white-space: nowrap;
flex-shrink: 0;
}
}
.conversation-summary-highlight-container {
font-size: 14px;
color: #6e7387;
white-space: normal;
overflow: hidden;
flex: 1;
text-align: left;
word-break: break-word;
}
}
&:hover {
opacity: 0.78;
}
&.selected {
background-color: transparent;
}
&:hover {opacity: .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; }
.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;
.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;}
&.me {
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%;
.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;}
.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%;
.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>
<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-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">
<button class="accordion-button collapsed" type="button" data-bs-toggle="collapse"
:data-bs-target="'#collapse' + index" aria-expanded="false" :aria-controls="'collapse' + index">
<button
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-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">
<span class="request-content-issuer-text">
{{ request.issuer_profile.first_name }}
{{ request.issuer_profile.last_name }}</span>
{{ request.issuer_profile.last_name }}</span
>
</div>
</div>
<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">
<span class="request-content-title-text"> {{ request.title }}</span>
</div>
@ -40,8 +61,11 @@
</div>
</button>
</h2>
<div :id="'collapse' + index" class="accordion-collapse collapse"
data-bs-parent="#request-invitation-container">
<div
:id="'collapse' + index"
class="accordion-collapse collapse"
data-bs-parent="#request-invitation-container"
>
<div class="accordion-body">
<div class="request-description-container">
<button class="make-proposal-button" type="button" @click="Propose(request)">
@ -49,51 +73,95 @@
</button>
<div class="request-description-content" v-html="request.content"></div>
<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="downloadAttachedFile(request.id, file.document_id)">Download{{ file.file_name }}</button>
<button @click="previewAttachedFile(request.id, file.document_id)">
Preview{{ file.file_name }}
</button>
<button @click="downloadAttachedFile(request.id, file.document_id)">
Download{{ file.file_name }}
</button>
</div>
</div>
<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-isssuer-container">
<label class="issuer-achievement-label" for="issuer-achievement-isssuer-content-div">Name</label>
<div class="issuer-achievement-content-container" id="issuer-achievement-isssuer-content-div">
<label
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">
{{ request.issuer_profile.first_name }}
{{ request.issuer_profile.last_name }}</span>
{{ request.issuer_profile.last_name }}</span
>
</div>
</div>
<div class="issuer-achievement-stay-container">
<label class="issuer-achievement-label" for="issuer-achievement-stay-content-div">Stay on
Freeleaps</label>
<div class="issuer-achievement-content-container" id="issuer-achievement-stay-content-div">
<label
class="issuer-achievement-label"
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">
{{ 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 class="issuer-achievement-paid-container">
<label class="issuer-achievement-label" for="issuer-achievement-paid-content-div">Total
payment</label>
<div class="issuer-achievement-content-container" id="issuer-achievement-stay-content-div">
<label
class="issuer-achievement-label"
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">
{{ request.issuer_achievement.issuer.spending.total }}
{{ request.issuer_achievement.issuer.spending.currency }}</span>
{{ request.issuer_achievement.issuer.spending.currency }}</span
>
</div>
</div>
<div class="issuer-achievement-deposit-container">
<label class="issuer-achievement-label" for="issuer-achievement-deposit-content-div">Deposit</label>
<div class="issuer-achievement-content-container" id="issuer-achievement-deposit-content-div">
<label
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">
{{ request.issuer_achievement.issuer.deposit.available }}
{{ request.issuer_achievement.issuer.deposit.currency }}</span>
{{ request.issuer_achievement.issuer.deposit.currency }}</span
>
</div>
</div>
<div class="issuer-achievement-credit-container">
<label class="issuer-achievement-label" for="issuer-achievement-credit-content-div">Credit</label>
<div class="issuer-achievement-content-container" id="issuer-achievement-credit-content-div">
<label
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">
{{ request.issuer_achievement.activeness.credit }}</span>
{{ request.issuer_achievement.activeness.credit }}</span
>
</div>
</div>
</div>
@ -140,36 +208,36 @@ export default {
return DateUtils.FromJsonToDateString(fulltime)
},
previewAttachedFile(request_id, document_id) {
WorksapceApi.fetchAttachedFileAsMediaData(request_id, document_id).then(response => {
let media_data = response.data
console.log(media_data)
//TODO: navigate to the preview page
}).catch((error) => {
this.mnx_backendErrorHandler(error)
}
)
WorksapceApi.fetchAttachedFileAsMediaData(request_id, document_id)
.then((response) => {
let media_data = response.data
console.log(media_data)
//TODO: navigate to the preview page
})
.catch((error) => {
this.mnx_backendErrorHandler(error)
})
},
downloadAttachedFile(request_id, document_id) {
WorksapceApi.fetchAttachedFileAsDownload(request_id, document_id).then(response => {
// create file link in browser's memory
const href = URL.createObjectURL(response.data);
// create "a" HTML element with href to file & click
const link = document.createElement('a');
link.href = href;
link.setAttribute('download', 'file.pdf'); //or any other extension
document.body.appendChild(link);
link.click();
// clean up "a" element & remove ObjectURL
document.body.removeChild(link);
URL.revokeObjectURL(href); //TODO: navigate to the preview page
}).catch((error) => {
this.mnx_backendErrorHandler(error)
}
)
WorksapceApi.fetchAttachedFileAsDownload(request_id, document_id)
.then((response) => {
// create file link in browser's memory
const href = URL.createObjectURL(response.data)
// create "a" HTML element with href to file & click
const link = document.createElement('a')
link.href = href
link.setAttribute('download', 'file.pdf') //or any other extension
document.body.appendChild(link)
link.click()
// clean up "a" element & remove ObjectURL
document.body.removeChild(link)
URL.revokeObjectURL(href) //TODO: navigate to the preview page
})
.catch((error) => {
this.mnx_backendErrorHandler(error)
})
}
}
}

View File

@ -911,24 +911,48 @@ export default {
</script>
<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 {
@extend .flex-row-container;
@extend .justify-content-end;
}
.workspace-body {width: 100%; max-width: $body-width; padding: 24px 0;
.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-body {
width: 100%;
max-width: $body-width;
padding: 24px 0;
.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 {
@extend .flex-row-container;

View File

@ -105,14 +105,23 @@
</div>
</div>
<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">
<label class="btn btn-link">
<span v-if="!uploadFile">Upload file</span>
<span v-if="uploadFile">{{uploadFile.name}}</span>
<input type="file" hidden @change="handleFileUpload"/>
<span v-if="uploadFile">{{ uploadFile.name }}</span>
<input type="file" hidden @change="handleFileUpload" />
</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 class="flex-1" />
<button
@ -176,7 +185,7 @@ export default {
type: String
}
},
components: {FreeleapsEditor, InputSelector, SvgIcon },
components: { FreeleapsEditor, InputSelector, SvgIcon },
mounted() {
this.initProducts()
this.initiateFromInput()
@ -208,7 +217,7 @@ export default {
initProducts() {
WorksapceApi.fetchProducts()
.then((response) => {
this.products = (response.data || []).map(p => p.name)
this.products = (response.data || []).map((p) => p.name)
})
.catch((error) => {
this.mnx_backendErrorHandler(error)
@ -325,9 +334,16 @@ export default {
}
</script>
<style lang="scss" scoped>
.upload-contianer {display: flex; align-items: center;}
.delete-icon {color: $primary; cursor: pointer;
&:hover {opacity: .78;}
.upload-contianer {
display: flex;
align-items: center;
}
.delete-icon {
color: $primary;
cursor: pointer;
&:hover {
opacity: 0.78;
}
}
.request-issue-container {
@extend .flex-colum-container;

View File

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