fix: profile routes

This commit is contained in:
Jannat Patel
2024-04-18 20:27:15 +05:30
parent 86713db75e
commit 44b36599c3
10 changed files with 2443 additions and 38 deletions

View File

@@ -10,10 +10,12 @@
}, },
"dependencies": { "dependencies": {
"@editorjs/checklist": "^1.6.0", "@editorjs/checklist": "^1.6.0",
"@editorjs/code": "^2.9.0",
"@editorjs/editorjs": "^2.29.0", "@editorjs/editorjs": "^2.29.0",
"@editorjs/embed": "^2.7.0", "@editorjs/embed": "^2.7.0",
"@editorjs/header": "^2.8.1", "@editorjs/header": "^2.8.1",
"@editorjs/image": "^2.9.0", "@editorjs/image": "^2.9.0",
"@editorjs/inline-code": "^1.5.0",
"@editorjs/nested-list": "^1.4.2", "@editorjs/nested-list": "^1.4.2",
"@editorjs/paragraph": "^2.11.3", "@editorjs/paragraph": "^2.11.3",
"chart.js": "^4.4.1", "chart.js": "^4.4.1",
@@ -25,7 +27,7 @@
"pinia": "^2.0.33", "pinia": "^2.0.33",
"socket.io-client": "^4.7.2", "socket.io-client": "^4.7.2",
"tailwindcss": "^3.3.3", "tailwindcss": "^3.3.3",
"vue": "^3.2.25", "vue": "^3.4.23",
"vue-chartjs": "^5.3.0", "vue-chartjs": "^5.3.0",
"vue-router": "^4.0.12" "vue-router": "^4.0.12"
}, },

View File

@@ -10,7 +10,13 @@
:style="{ backgroundImage: 'url(' + encodeURI(course.image) + ')' }" :style="{ backgroundImage: 'url(' + encodeURI(course.image) + ')' }"
> >
<div class="flex relative top-4 left-4 w-fit flex-wrap"> <div class="flex relative top-4 left-4 w-fit flex-wrap">
<Badge theme="gray" size="md" class="mr-2" v-for="tag in course.tags"> <Badge
variant="outline"
theme="gray"
size="md"
class="mr-2"
v-for="tag in course.tags"
>
{{ tag }} {{ tag }}
</Badge> </Badge>
</div> </div>
@@ -89,17 +95,7 @@
:user="instructor" :user="instructor"
/> />
</div> </div>
<span v-if="course.instructors.length == 1"> <CourseInstructors :instructors="course.instructors" />
{{ course.instructors[0].full_name }}
</span>
<span v-if="course.instructors.length == 2">
{{ course.instructors[0].first_name }} and
{{ course.instructors[1].first_name }}
</span>
<span v-if="course.instructors.length > 2">
{{ course.instructors[0].first_name }} and
{{ course.instructors.length - 1 }} others
</span>
</div> </div>
<div class="font-semibold"> <div class="font-semibold">
@@ -114,6 +110,7 @@ import { BookOpen, Users, Star } from 'lucide-vue-next'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
import { sessionStore } from '@/stores/session' import { sessionStore } from '@/stores/session'
import { Badge, Tooltip } from 'frappe-ui' import { Badge, Tooltip } from 'frappe-ui'
import CourseInstructors from '@/components/CourseInstructors.vue'
const { user } = sessionStore() const { user } = sessionStore()

View File

@@ -0,0 +1,50 @@
<template>
<span v-if="instructors.length == 1">
<router-link
:to="{
name: 'Profile',
params: { username: instructors[0].username },
}"
>
{{ instructors[0].full_name }}
</router-link>
</span>
<span v-if="instructors.length == 2">
<router-link
:to="{
name: 'Profile',
params: { username: instructors[0].username },
}"
>
{{ instructors[0].first_name }}
</router-link>
and
<router-link
:to="{
name: 'Profile',
params: { username: instructors[1].username },
}"
>
{{ instructors[1].first_name }}
</router-link>
</span>
<span v-if="instructors.length > 2">
<router-link
:to="{
name: 'Profile',
params: { username: instructors[0].username },
}"
>
{{ instructors[0].first_name }}
</router-link>
and {{ instructors.length - 1 }} others
</span>
</template>
<script setup>
const props = defineProps({
instructors: {
type: Array,
required: true,
},
})
</script>

View File

@@ -17,9 +17,16 @@
<div class="flex items-center"> <div class="flex items-center">
<UserAvatar :user="review.owner_details" :size="'2xl'" /> <UserAvatar :user="review.owner_details" :size="'2xl'" />
<div class="mx-4"> <div class="mx-4">
<span class="text-lg font-medium mr-4"> <router-link
{{ review.owner_details.full_name }} :to="{
</span> name: 'Profile',
params: { username: review.owner_details.username },
}"
>
<span class="text-lg font-medium mr-4">
{{ review.owner_details.full_name }}
</span>
</router-link>
<span> <span>
{{ review.creation }} {{ review.creation }}
</span> </span>

View File

@@ -51,17 +51,7 @@
:user="instructor" :user="instructor"
/> />
</span> </span>
<span v-if="course.data.instructors.length == 1"> <CourseInstructors :instructors="course.data.instructors" />
{{ course.data.instructors[0].full_name }}
</span>
<span v-if="course.data.instructors.length == 2">
{{ course.data.instructors[0].first_name }} and
{{ course.data.instructors[1].first_name }}
</span>
<span v-if="course.data.instructors.length > 2">
{{ course.data.instructors[0].first_name }} and
{{ course.data.instructors.length - 1 }} others
</span>
</div> </div>
</div> </div>
<div class="flex mt-3 mb-4 w-fit"> <div class="flex mt-3 mb-4 w-fit">
@@ -109,6 +99,7 @@ import CourseOutline from '@/components/CourseOutline.vue'
import CourseReviews from '@/components/CourseReviews.vue' import CourseReviews from '@/components/CourseReviews.vue'
import UserAvatar from '@/components/UserAvatar.vue' import UserAvatar from '@/components/UserAvatar.vue'
import { updateDocumentTitle } from '@/utils' import { updateDocumentTitle } from '@/utils'
import CourseInstructors from '@/components/CourseInstructors.vue'
const props = defineProps({ const props = defineProps({
courseName: { courseName: {

View File

@@ -446,4 +446,19 @@ const breadcrumbs = computed(() => {
.ce-block__content { .ce-block__content {
max-width: none; max-width: none;
} }
.ce-code__textarea {
min-height: 200px;
font-family: Menlo, Monaco, Consolas, Courier New, monospace;
color: #41314e;
line-height: 1.6em;
font-size: 12px;
background: #f8f7fa;
border: 1px solid #f1f1f4;
box-shadow: none;
white-space: pre;
word-wrap: normal;
overflow-x: auto;
resize: vertical;
}
</style> </style>

View File

@@ -101,17 +101,7 @@
:user="instructor" :user="instructor"
/> />
</span> </span>
<span v-if="lesson.data.instructors.length == 1"> <CourseInstructors :instructors="lesson.data.instructors" />
{{ lesson.data.instructors[0].full_name }}
</span>
<span v-if="lesson.data.instructors.length == 2">
{{ lesson.data.instructors[0].first_name }} and
{{ lesson.data.instructors[1].first_name }}
</span>
<span v-if="lesson.data.instructors.length > 2">
{{ lesson.data.instructors[0].first_name }} and
{{ lesson.data.instructors.length - 1 }} others
</span>
</div> </div>
<div <div
v-if=" v-if="
@@ -196,6 +186,7 @@ import Discussions from '@/components/Discussions.vue'
import { getEditorTools } from '../utils' import { getEditorTools } from '../utils'
import EditorJS from '@editorjs/editorjs' import EditorJS from '@editorjs/editorjs'
import LessonContent from '@/components/LessonContent.vue' import LessonContent from '@/components/LessonContent.vue'
import CourseInstructors from '@/components/CourseInstructors.vue'
const user = inject('$user') const user = inject('$user')
const route = useRoute() const route = useRoute()
@@ -399,4 +390,19 @@ const allowInstructorContent = () => {
.ce-block__content { .ce-block__content {
max-width: unset; max-width: unset;
} }
.ce-code__textarea {
min-height: 200px;
font-family: Menlo, Monaco, Consolas, Courier New, monospace;
color: #41314e;
line-height: 1.6em;
font-size: 12px;
background: #f8f7fa;
border: 1px solid #f1f1f4;
box-shadow: none;
white-space: pre;
word-wrap: normal;
overflow-x: auto;
resize: vertical;
}
</style> </style>

233
frontend/src/utils/code.js Normal file
View File

@@ -0,0 +1,233 @@
import { getLineStartPosition } from './index.js'
export class CodeTool {
static get isReadOnlySupported() {
return true
}
static get enableLineBreaks() {
return true
}
constructor({ data, config, api, readOnly }) {
this.api = api
this.readOnly = readOnly
this.placeholder = this.api.i18n.t(
config.placeholder || CodeTool.DEFAULT_PLACEHOLDER
)
this.CSS = {
baseClass: this.api.styles.block,
input: this.api.styles.input,
wrapper: 'ce-code',
textarea: 'ce-code__textarea',
select: 'ce-code__languagecode',
}
if (config.languageList) {
this.languageList = config.languageList
} else {
this.languageList = [
{ name: 'Select Language', code: '' },
{ name: 'HTML', code: 'html' },
{ name: 'CSS', code: 'css' },
{ name: 'JavaScript', code: 'js' },
{ name: 'C#', code: 'csharp' },
{ name: 'ASP.NET (C#)', code: 'aspnet' },
{ name: 'Docker', code: 'docker' },
{ name: 'Git', code: 'git' },
{ name: 'Java', code: 'java' },
{ name: 'JSON', code: 'json' },
{ name: 'JSONP', code: 'jsonp' },
{ name: 'JSON5', code: 'json5' },
{ name: 'PowerShell', code: 'powershell' },
{ name: 'Python', code: 'python' },
{ name: 'React JSX', code: 'jsx' },
{ name: 'React TSX', code: 'tsx' },
{ name: 'Regex', code: 'regex' },
{ name: 'Sass (Sass)', code: 'sass' },
{ name: 'Sass (Scss)', code: 'scss' },
{ name: 'SQL', code: 'sql' },
{ name: 'TypeScript', code: 'typescript' },
{ name: 'YAML', code: 'yaml' },
]
}
if (config.additionalLanguages) {
this.languageList.push(...config.additionalLanguages)
}
this.nodes = {
holder: null,
textarea: null,
picker: null,
}
this.data = {
code: data.code || '',
languageCode: data.languageCode?.replace('language-', '') || '',
}
this.nodes.holder = this.drawView()
}
drawView() {
const wrapper = document.createElement('div'),
textarea = document.createElement('textarea'),
picker = document.createElement('select')
this.languageList.forEach((item) => {
let option = document.createElement('option')
option.text = item.name
option.value = item.code
picker.appendChild(option)
})
wrapper.classList.add(this.CSS.baseClass, this.CSS.wrapper)
textarea.classList.add(this.CSS.textarea, this.CSS.input)
textarea.textContent = this.data.code
picker.value = this.data.languageCode
textarea.placeholder = this.placeholder
if (this.readOnly) {
textarea.disabled = true
picker.disabled = true
}
wrapper.appendChild(picker)
wrapper.appendChild(textarea)
textarea.addEventListener('keydown', (event) => {
switch (event.code) {
case 'Tab':
this.tabHandler(event)
break
}
})
this.nodes.textarea = textarea
this.nodes.picker = picker
return wrapper
}
render() {
return this.nodes.holder
}
save(codeWrapper) {
return {
code: codeWrapper.querySelector('textarea').value,
languageCode: `language-${
codeWrapper.querySelector('select').value
}`,
}
}
onPaste(event) {
const content = event.detail.data
this.data = {
code: content.textContent,
}
}
get data() {
return this._data
}
set data(data) {
this._data = data
if (this.nodes.textarea) {
this.nodes.textarea.textContent = data.code
}
if (this.nodes.picker) {
this.nodes.picker.value = data.languageCode
}
}
static get toolbox() {
return {
icon: '<svg width="14" height="14" viewBox="0 -1 14 14" xmlns="http://www.w3.org/2000/svg" > <path d="M3.177 6.852c.205.253.347.572.427.954.078.372.117.844.117 1.417 0 .418.01.725.03.92.02.18.057.314.107.396.046.075.093.117.14.134.075.027.218.056.42.083a.855.855 0 0 1 .56.297c.145.167.215.38.215.636 0 .612-.432.934-1.216.934-.457 0-.87-.087-1.233-.262a1.995 1.995 0 0 1-.853-.751 2.09 2.09 0 0 1-.305-1.097c-.014-.648-.029-1.168-.043-1.56-.013-.383-.034-.631-.06-.733-.064-.263-.158-.455-.276-.578a2.163 2.163 0 0 0-.505-.376c-.238-.134-.41-.256-.519-.371C.058 6.76 0 6.567 0 6.315c0-.37.166-.657.493-.846.329-.186.56-.342.693-.466a.942.942 0 0 0 .26-.447c.056-.2.088-.42.097-.658.01-.25.024-.85.043-1.802.015-.629.239-1.14.672-1.522C2.691.19 3.268 0 3.977 0c.783 0 1.216.317 1.216.921 0 .264-.069.48-.211.643a.858.858 0 0 1-.563.29c-.249.03-.417.076-.498.126-.062.04-.112.134-.139.291-.031.187-.052.562-.061 1.119a8.828 8.828 0 0 1-.112 1.378 2.24 2.24 0 0 1-.404.963c-.159.212-.373.406-.64.583.25.163.454.342.612.538zm7.34 0c.157-.196.362-.375.612-.538a2.544 2.544 0 0 1-.641-.583 2.24 2.24 0 0 1-.404-.963 8.828 8.828 0 0 1-.112-1.378c-.009-.557-.03-.932-.061-1.119-.027-.157-.077-.251-.14-.29-.08-.051-.248-.096-.496-.127a.858.858 0 0 1-.564-.29C8.57 1.401 8.5 1.185 8.5.921 8.5.317 8.933 0 9.716 0c.71 0 1.286.19 1.72.574.432.382.656.893.671 1.522.02.952.033 1.553.043 1.802.009.238.041.458.097.658a.942.942 0 0 0 .26.447c.133.124.364.28.693.466a.926.926 0 0 1 .493.846c0 .252-.058.446-.183.58-.109.115-.281.237-.52.371-.21.118-.377.244-.504.376-.118.123-.212.315-.277.578-.025.102-.045.35-.06.733-.013.392-.027.912-.042 1.56a2.09 2.09 0 0 1-.305 1.097c-.2.323-.486.574-.853.75a2.811 2.811 0 0 1-1.233.263c-.784 0-1.216-.322-1.216-.934 0-.256.07-.47.214-.636a.855.855 0 0 1 .562-.297c.201-.027.344-.056.418-.083.048-.017.096-.06.14-.134a.996.996 0 0 0 .107-.396c.02-.195.031-.502.031-.92 0-.573.039-1.045.117-1.417.08-.382.222-.701.427-.954z" /> </svg>',
title: 'Code',
}
}
static get DEFAULT_PLACEHOLDER() {
return 'Enter a code'
}
static get pasteConfig() {
return {
tags: ['pre'],
}
}
static get sanitize() {
return {
code: true, // Allow HTML tags
}
}
tabHandler(event) {
/**
* Prevent editor.js tab handler
*/
event.stopPropagation()
/**
* Prevent native tab behaviour
*/
event.preventDefault()
const textarea = event.target
const isShiftPressed = event.shiftKey
const caretPosition = textarea.selectionStart
const value = textarea.value
const indentation = ' '
let newCaretPosition
/**
* For Tab pressing, just add an indentation to the caret position
*/
if (!isShiftPressed) {
newCaretPosition = caretPosition + indentation.length
textarea.value =
value.substring(0, caretPosition) +
indentation +
value.substring(caretPosition)
} else {
/**
* For Shift+Tab pressing, remove an indentation from the start of line
*/
const currentLineStart = getLineStartPosition(value, caretPosition)
const firstLineChars = value.substr(
currentLineStart,
indentation.length
)
if (firstLineChars !== indentation) {
return
}
/**
* Trim the first two chars from the start of line
*/
textarea.value =
value.substring(0, currentLineStart) +
value.substring(currentLineStart + indentation.length)
newCaretPosition = caretPosition - indentation.length
}
/**
* Restore the caret
*/
textarea.setSelectionRange(newCaretPosition, newCaretPosition)
}
}

View File

@@ -7,6 +7,7 @@ import Header from '@editorjs/header'
import Paragraph from '@editorjs/paragraph' import Paragraph from '@editorjs/paragraph'
import Embed from '@editorjs/embed' import Embed from '@editorjs/embed'
import NestedList from '@editorjs/nested-list' import NestedList from '@editorjs/nested-list'
import InlineCode from '@editorjs/inline-code'
import { watch } from 'vue' import { watch } from 'vue'
import dayjs from '@/utils/dayjs' import dayjs from '@/utils/dayjs'
@@ -136,6 +137,10 @@ export function getEditorTools() {
defaultStyle: 'ordered', defaultStyle: 'ordered',
}, },
}, },
inlineCode: {
class: InlineCode,
shortcut: 'CMD+SHIFT+M',
},
embed: { embed: {
class: Embed, class: Embed,
inlineToolbar: false, inlineToolbar: false,
@@ -336,3 +341,19 @@ export function getFormattedDateRange(
format format
)}` )}`
} }
export function getLineStartPosition(string, position) {
const charLength = 1
let char = ''
while (char !== '\n' && position > 0) {
position = position - charLength
char = string.substr(position, charLength)
}
if (char === '\n') {
position += 1
}
return position
}

2083
frontend/yarn.lock Normal file

File diff suppressed because it is too large Load Diff