mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-09 02:24:16 +00:00
merge from local
This commit is contained in:
parent
17892508e7
commit
00f6e34c53
@ -1,4 +1,13 @@
|
||||
services:
|
||||
app:
|
||||
build: packages/app
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- internal_network
|
||||
ports:
|
||||
- "3000:3000"
|
||||
env_file:
|
||||
- ./.env
|
||||
api:
|
||||
build: packages/server
|
||||
restart: unless-stopped
|
||||
|
@ -1,4 +1,4 @@
|
||||
FROM node:16-alpine
|
||||
FROM node:20-alpine
|
||||
|
||||
RUN apk add git
|
||||
RUN mkdir -p /home/node/app/node_modules && chown -R node:node /home/node/app
|
||||
|
@ -4,17 +4,6 @@
|
||||
"label": "Search",
|
||||
"icon": "FiSearch"
|
||||
},
|
||||
{
|
||||
"id": "messages",
|
||||
"label": "Messages",
|
||||
"icon": "FiMessageCircle",
|
||||
"path": "/messages"
|
||||
},
|
||||
{
|
||||
"id": "notifications",
|
||||
"label": "Notifications",
|
||||
"icon": "FiBell"
|
||||
},
|
||||
{
|
||||
"id": "settings",
|
||||
"label": "Settings",
|
||||
|
@ -45,7 +45,7 @@
|
||||
"fast-average-color": "^9.2.0",
|
||||
"framer-motion": "^10.12.17",
|
||||
"fuse.js": "6.5.3",
|
||||
"hls.js": "^1.5.15",
|
||||
"hls.js": "^1.5.17",
|
||||
"howler": "2.2.3",
|
||||
"i18next": "21.6.6",
|
||||
"js-cookie": "3.0.1",
|
||||
|
@ -75,17 +75,9 @@ const Attachment = React.memo((props) => {
|
||||
return <ImageViewer src={url} />
|
||||
}
|
||||
case "video": {
|
||||
return <Plyr
|
||||
source={{
|
||||
type: "video",
|
||||
sources: [{
|
||||
src: url,
|
||||
}],
|
||||
}}
|
||||
options={{
|
||||
controls: ["play", "progress", "current-time", "mute", "volume"],
|
||||
}}
|
||||
/>
|
||||
return <video controls>
|
||||
<source src={url} type={mimeType} />
|
||||
</video>
|
||||
}
|
||||
case "audio": {
|
||||
return <audio controls>
|
||||
|
@ -68,6 +68,7 @@
|
||||
|
||||
video {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
|
||||
user-select: none;
|
||||
-webkit-user-drag: none;
|
||||
|
@ -12,6 +12,7 @@ import Poll from "@components/Poll"
|
||||
import clipboardEventFileToFile from "@utils/clipboardEventFileToFile"
|
||||
|
||||
import PostModel from "@models/post"
|
||||
import SearchModel from "@models/search"
|
||||
|
||||
import "./index.less"
|
||||
|
||||
@ -33,6 +34,8 @@ export default class PostCreator extends React.Component {
|
||||
|
||||
fileList: [],
|
||||
postingPolicy: DEFAULT_POST_POLICY,
|
||||
|
||||
mentionsLoadedData: []
|
||||
}
|
||||
|
||||
pollRef = React.createRef()
|
||||
@ -59,14 +62,6 @@ export default class PostCreator extends React.Component {
|
||||
})
|
||||
}
|
||||
|
||||
fetchUploadPolicy = async () => {
|
||||
const policy = await PostModel.getPostingPolicy()
|
||||
|
||||
this.setState({
|
||||
postingPolicy: policy
|
||||
})
|
||||
}
|
||||
|
||||
canSubmit = () => {
|
||||
const { postMessage, postAttachments, pending, postingPolicy } = this.state
|
||||
|
||||
@ -83,9 +78,7 @@ export default class PostCreator extends React.Component {
|
||||
return true
|
||||
}
|
||||
|
||||
debounceSubmit = lodash.debounce(() => this.submit(), 50)
|
||||
|
||||
submit = async () => {
|
||||
submit = lodash.debounce(async () => {
|
||||
if (this.state.loading) {
|
||||
return false
|
||||
}
|
||||
@ -154,10 +147,9 @@ export default class PostCreator extends React.Component {
|
||||
app.navigation.goToPost(this.props.reply_to)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 50)
|
||||
|
||||
uploadFile = async (req) => {
|
||||
// hide uploader
|
||||
this.toggleUploaderVisibility(false)
|
||||
|
||||
const request = await app.cores.remoteStorage.uploadFile(req.file)
|
||||
@ -265,18 +257,18 @@ export default class PostCreator extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
onChangeMessageInput = (event) => {
|
||||
handleMessageInputChange = (inputText) => {
|
||||
// if the fist character is a space or a whitespace remove it
|
||||
if (event.target.value[0] === " " || event.target.value[0] === "\n") {
|
||||
event.target.value = event.target.value.slice(1)
|
||||
if (inputText[0] === " " || inputText[0] === "\n") {
|
||||
inputText = inputText.slice(1)
|
||||
}
|
||||
|
||||
this.setState({
|
||||
postMessage: event.target.value
|
||||
postMessage: inputText
|
||||
})
|
||||
}
|
||||
|
||||
handleKeyDown = async (e) => {
|
||||
handleMessageInputKeydown = async (e) => {
|
||||
// detect if the user pressed `enter` key and submit the form, but only if the `shift` key is not pressed
|
||||
if (e.keyCode === 13 && !e.shiftKey) {
|
||||
e.preventDefault()
|
||||
@ -290,6 +282,15 @@ export default class PostCreator extends React.Component {
|
||||
}
|
||||
}
|
||||
|
||||
handleOnMentionSearch = lodash.debounce(async (value) => {
|
||||
const results = await SearchModel.userSearch(`username:${value}`)
|
||||
|
||||
this.setState({
|
||||
mentionsLoadedData: results
|
||||
})
|
||||
|
||||
}, 300)
|
||||
|
||||
updateFileList = (uid, newValue) => {
|
||||
let updatedFileList = this.state.fileList
|
||||
|
||||
@ -577,16 +578,28 @@ export default class PostCreator extends React.Component {
|
||||
<img src={app.userData?.avatar} />
|
||||
</div>
|
||||
|
||||
<antd.Input.TextArea
|
||||
<antd.Mentions
|
||||
placeholder="What are you thinking?"
|
||||
value={postMessage}
|
||||
autoSize={{ minRows: 3, maxRows: 6 }}
|
||||
maxLength={postingPolicy.maxMessageLength}
|
||||
onChange={this.onChangeMessageInput}
|
||||
onKeyDown={this.handleKeyDown}
|
||||
onChange={this.handleMessageInputChange}
|
||||
onKeyDown={this.handleMessageInputKeydown}
|
||||
disabled={loading}
|
||||
draggable={false}
|
||||
prefix="@"
|
||||
allowClear
|
||||
options={this.state.mentionsLoadedData.map((item) => {
|
||||
return {
|
||||
key: item.id,
|
||||
value: item.username,
|
||||
label: <>
|
||||
<antd.Avatar src={item.avatar} />
|
||||
<span>{item.username}</span>
|
||||
</>,
|
||||
}
|
||||
})}
|
||||
onSearch={this.handleOnMentionSearch}
|
||||
/>
|
||||
|
||||
<div>
|
||||
|
@ -21,6 +21,9 @@ const TrendingsCard = (props) => {
|
||||
{
|
||||
E_Trendings && <span>Something went wrong</span>
|
||||
}
|
||||
{
|
||||
!L_Trendings && !E_Trendings && R_Trendings && R_Trendings.length === 0 && <span>No trendings</span>
|
||||
}
|
||||
{
|
||||
!L_Trendings && !E_Trendings && R_Trendings && R_Trendings.map((trending, index) => {
|
||||
return <div
|
||||
|
@ -273,7 +273,7 @@ export default class Player extends Core {
|
||||
})
|
||||
}
|
||||
|
||||
async playbackMode(mode) {
|
||||
playbackMode(mode) {
|
||||
if (typeof mode !== "string") {
|
||||
return this.state.playback_mode
|
||||
}
|
||||
@ -289,7 +289,7 @@ export default class Player extends Core {
|
||||
return mode
|
||||
}
|
||||
|
||||
async stopPlayback() {
|
||||
stopPlayback() {
|
||||
if (this.queue.currentItem) {
|
||||
this.queue.currentItem.stop()
|
||||
}
|
||||
|
@ -131,7 +131,6 @@ export default class StyleCore extends Core {
|
||||
|
||||
mutateTheme: (...args) => this.mutateTheme(...args),
|
||||
resetToDefault: () => this.resetToDefault(),
|
||||
toggleCompactMode: () => this.toggleCompactMode(),
|
||||
}
|
||||
|
||||
async onInitialize() {
|
||||
@ -251,20 +250,6 @@ export default class StyleCore extends Core {
|
||||
StyleCore.storagedModifications = this.public.mutation
|
||||
}
|
||||
|
||||
toggleCompactMode(value = !window.app.cores.settings.get("style.compactMode")) {
|
||||
if (value === true) {
|
||||
return this.applyStyles({
|
||||
layoutMargin: 0,
|
||||
layoutPadding: 0,
|
||||
})
|
||||
}
|
||||
|
||||
return this.applyStyles({
|
||||
layoutMargin: this.getVar("layoutMargin"),
|
||||
layoutPadding: this.getVar("layoutPadding"),
|
||||
})
|
||||
}
|
||||
|
||||
resetToDefault() {
|
||||
store.remove(StyleCore.modificationStorageKey)
|
||||
|
||||
|
@ -21,8 +21,6 @@ export default class WidgetsCore extends Core {
|
||||
|
||||
async onInitialize() {
|
||||
try {
|
||||
//await WidgetsCore.apiInstance()
|
||||
|
||||
const currentStore = this.getInstalled()
|
||||
|
||||
if (!Array.isArray(currentStore)) {
|
||||
|
@ -3,14 +3,14 @@ import React from "react"
|
||||
export default () => {
|
||||
const enterPlayerAnimation = () => {
|
||||
app.cores.style.applyTemporalVariant("dark")
|
||||
app.cores.style.toggleCompactMode(true)
|
||||
app.layout.toggleCompactMode(true)
|
||||
app.layout.toggleCenteredContent(false)
|
||||
app.controls.toggleUIVisibility(false)
|
||||
}
|
||||
|
||||
const exitPlayerAnimation = () => {
|
||||
app.cores.style.applyVariant(app.cores.style.getStoragedVariantKey())
|
||||
app.cores.style.toggleCompactMode(false)
|
||||
app.layout.toggleCompactMode(false)
|
||||
app.layout.toggleCenteredContent(true)
|
||||
app.controls.toggleUIVisibility(true)
|
||||
}
|
||||
|
@ -108,6 +108,9 @@ export default class Layout extends React.PureComponent {
|
||||
togglePagePanelSpacer: (to) => {
|
||||
return this.layoutInterface.toggleRootContainerClassname("page-panel-spacer", to)
|
||||
},
|
||||
toggleCompactMode: (to) => {
|
||||
return this.layoutInterface.toggleRootContainerClassname("compact-mode", to)
|
||||
},
|
||||
toggleRootContainerClassname: (classname, to) => {
|
||||
const root = document.documentElement
|
||||
|
||||
|
@ -61,10 +61,14 @@ export default class ToolsBar extends React.Component {
|
||||
}
|
||||
|
||||
render() {
|
||||
const hasAnyRenders = this.state.renders.top.length > 0 || this.state.renders.bottom.length > 0
|
||||
|
||||
const isVisible = hasAnyRenders && this.state.visible
|
||||
|
||||
return <Motion
|
||||
style={{
|
||||
x: spring(this.state.visible ? 0 : 100),
|
||||
width: spring(this.state.visible ? 100 : 0),
|
||||
x: spring(isVisible ? 0 : 100),
|
||||
width: spring(isVisible ? 100 : 0),
|
||||
}}
|
||||
>
|
||||
{({ x, width }) => {
|
||||
@ -76,7 +80,7 @@ export default class ToolsBar extends React.Component {
|
||||
className={classnames(
|
||||
"tools-bar-wrapper",
|
||||
{
|
||||
visible: this.state.visible,
|
||||
visible: isVisible,
|
||||
}
|
||||
)}
|
||||
>
|
||||
|
@ -18,34 +18,6 @@ function isOverflown(element) {
|
||||
return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
|
||||
}
|
||||
|
||||
const RenderArtist = (props) => {
|
||||
const { artist } = props
|
||||
|
||||
if (!artist) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (Array.isArray(artist)) {
|
||||
return <h3>{artist.join(",")}</h3>
|
||||
}
|
||||
|
||||
return <h3>{artist}</h3>
|
||||
}
|
||||
|
||||
const RenderAlbum = (props) => {
|
||||
const { album } = props
|
||||
|
||||
if (!album) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (Array.isArray(album)) {
|
||||
return <h3>{album.join(",")}</h3>
|
||||
}
|
||||
|
||||
return <h3>{album}</h3>
|
||||
}
|
||||
|
||||
const PlayerController = React.forwardRef((props, ref) => {
|
||||
const [playerState] = usePlayerStateContext()
|
||||
|
||||
@ -152,9 +124,7 @@ const PlayerController = React.forwardRef((props, ref) => {
|
||||
</div>
|
||||
|
||||
<div className="lyrics-player-controller-info-details">
|
||||
<RenderArtist artist={playerState.track_manifest?.artists} />
|
||||
-
|
||||
<RenderAlbum album={playerState.track_manifest?.album} />
|
||||
<span>{playerState.track_manifest?.artistStr}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -11,6 +11,7 @@ const LyricsVideo = React.forwardRef((props, videoRef) => {
|
||||
|
||||
const { lyrics } = props
|
||||
|
||||
const [initialLoading, setInitialLoading] = React.useState(true)
|
||||
const [syncInterval, setSyncInterval] = React.useState(null)
|
||||
const [syncingVideo, setSyncingVideo] = React.useState(false)
|
||||
const [currentVideoLatency, setCurrentVideoLatency] = React.useState(0)
|
||||
@ -29,40 +30,36 @@ const LyricsVideo = React.forwardRef((props, videoRef) => {
|
||||
|
||||
setSyncingVideo(true)
|
||||
|
||||
videoRef.current.currentTime = currentTrackTime + (lyrics.sync_audio_at_ms / 1000) + app.cores.player.gradualFadeMs / 1000
|
||||
let newTime = currentTrackTime + (lyrics.sync_audio_at_ms / 1000) + app.cores.player.gradualFadeMs / 1000
|
||||
|
||||
// dec some ms to ensure the video seeks correctly
|
||||
newTime -= 5 / 1000
|
||||
|
||||
videoRef.current.currentTime = newTime
|
||||
}
|
||||
|
||||
async function syncPlayback() {
|
||||
if (!lyrics) {
|
||||
// if something is wrong, stop syncing
|
||||
if (videoRef.current === null || !lyrics || !lyrics.video_source || typeof lyrics.sync_audio_at_ms === "undefined" || playerState.playback_status !== "playing") {
|
||||
return stopSyncInterval()
|
||||
}
|
||||
|
||||
const currentTrackTime = app.cores.player.controls.seek()
|
||||
const currentVideoTime = videoRef.current.currentTime - (lyrics.sync_audio_at_ms / 1000)
|
||||
|
||||
//console.log(`Current track time: ${currentTrackTime}, current video time: ${currentVideoTime}`)
|
||||
|
||||
const maxOffset = maxLatencyInMs / 1000
|
||||
const currentVideoTimeDiff = Math.abs(currentVideoTime - currentTrackTime)
|
||||
|
||||
setCurrentVideoLatency(currentVideoTimeDiff)
|
||||
|
||||
if (syncingVideo === true) {
|
||||
return false
|
||||
}
|
||||
|
||||
// if `sync_audio_at_ms` is present, it means the video must be synced with audio
|
||||
if (lyrics.video_source && typeof lyrics.sync_audio_at_ms !== "undefined") {
|
||||
if (!videoRef.current) {
|
||||
clearInterval(syncInterval)
|
||||
setSyncInterval(null)
|
||||
setCurrentVideoLatency(0)
|
||||
return false
|
||||
}
|
||||
|
||||
const currentTrackTime = app.cores.player.controls.seek()
|
||||
const currentVideoTime = videoRef.current.currentTime - (lyrics.sync_audio_at_ms / 1000)
|
||||
|
||||
//console.log(`Current track time: ${currentTrackTime}, current video time: ${currentVideoTime}`)
|
||||
|
||||
const maxOffset = maxLatencyInMs / 1000
|
||||
const currentVideoTimeDiff = Math.abs(currentVideoTime - currentTrackTime)
|
||||
|
||||
setCurrentVideoLatency(currentVideoTimeDiff)
|
||||
|
||||
if (syncingVideo === true) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (currentVideoTimeDiff > maxOffset) {
|
||||
seekVideoToSyncAudio()
|
||||
}
|
||||
if (currentVideoTimeDiff > maxOffset) {
|
||||
seekVideoToSyncAudio()
|
||||
}
|
||||
}
|
||||
|
||||
@ -70,45 +67,13 @@ const LyricsVideo = React.forwardRef((props, videoRef) => {
|
||||
setSyncInterval(setInterval(syncPlayback, 300))
|
||||
}
|
||||
|
||||
React.useEffect(() => {
|
||||
videoRef.current.addEventListener("seeked", (event) => {
|
||||
setSyncingVideo(false)
|
||||
})
|
||||
|
||||
// videoRef.current.addEventListener("error", (event) => {
|
||||
// console.log("Failed to load", event)
|
||||
// })
|
||||
|
||||
// videoRef.current.addEventListener("ended", (event) => {
|
||||
// console.log("Video ended", event)
|
||||
// })
|
||||
|
||||
// videoRef.current.addEventListener("stalled", (event) => {
|
||||
// console.log("Failed to fetch data, but trying")
|
||||
// })
|
||||
|
||||
// videoRef.current.addEventListener("waiting", (event) => {
|
||||
// console.log("Waiting for data...")
|
||||
// })
|
||||
}, [])
|
||||
|
||||
//* Handle when playback status change
|
||||
React.useEffect(() => {
|
||||
if (lyrics?.video_source && typeof lyrics?.sync_audio_at_ms !== "undefined") {
|
||||
if (playerState.playback_status === "playing") {
|
||||
videoRef.current.play()
|
||||
|
||||
setSyncInterval(setInterval(syncPlayback, 500))
|
||||
} else {
|
||||
videoRef.current.pause()
|
||||
|
||||
if (syncInterval) {
|
||||
clearInterval(syncInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [playerState.playback_status])
|
||||
function stopSyncInterval() {
|
||||
setSyncingVideo(false)
|
||||
setSyncInterval(null)
|
||||
clearInterval(syncInterval)
|
||||
}
|
||||
|
||||
//* handle when player is loading
|
||||
React.useEffect(() => {
|
||||
if (lyrics?.video_source && playerState.loading === true && playerState.playback_status === "playing") {
|
||||
videoRef.current.pause()
|
||||
@ -119,50 +84,61 @@ const LyricsVideo = React.forwardRef((props, videoRef) => {
|
||||
}
|
||||
}, [playerState.loading])
|
||||
|
||||
//* Handle when playback status change
|
||||
React.useEffect(() => {
|
||||
if (initialLoading === false) {
|
||||
console.log(`VIDEO:: Playback status changed to ${playerState.playback_status}`)
|
||||
|
||||
if (lyrics && lyrics.video_source) {
|
||||
if (playerState.playback_status === "playing") {
|
||||
videoRef.current.play()
|
||||
startSyncInterval()
|
||||
} else {
|
||||
videoRef.current.pause()
|
||||
stopSyncInterval()
|
||||
}
|
||||
}
|
||||
}
|
||||
}, [playerState.playback_status])
|
||||
|
||||
//* Handle when lyrics object change
|
||||
React.useEffect(() => {
|
||||
clearInterval(syncInterval)
|
||||
setCurrentVideoLatency(0)
|
||||
setSyncingVideo(false)
|
||||
stopSyncInterval()
|
||||
|
||||
if (lyrics) {
|
||||
if (lyrics.video_source) {
|
||||
console.log("Loading video source >", lyrics.video_source)
|
||||
hls.current.loadSource(lyrics.video_source)
|
||||
|
||||
if (typeof lyrics.sync_audio_at_ms !== "undefined") {
|
||||
videoRef.current.loop = false
|
||||
videoRef.current.currentTime = lyrics.sync_audio_at_ms / 1000
|
||||
|
||||
if (playerState.playback_status === "playing") {
|
||||
videoRef.current.play()
|
||||
startSyncInterval()
|
||||
} else {
|
||||
videoRef.current.pause()
|
||||
}
|
||||
|
||||
const currentTime = app.cores.player.controls.seek()
|
||||
|
||||
if (currentTime > 0) {
|
||||
seekVideoToSyncAudio()
|
||||
}
|
||||
startSyncInterval()
|
||||
} else {
|
||||
videoRef.current.loop = true
|
||||
videoRef.current.currentTime = 0
|
||||
}
|
||||
|
||||
if (playerState.playback_status === "playing"){
|
||||
videoRef.current.play()
|
||||
}
|
||||
} else {
|
||||
videoRef.current
|
||||
}
|
||||
} else {
|
||||
videoRef.current
|
||||
}
|
||||
|
||||
setInitialLoading(false)
|
||||
}, [lyrics])
|
||||
|
||||
React.useEffect(() => {
|
||||
clearInterval(syncInterval)
|
||||
videoRef.current.addEventListener("seeked", (event) => {
|
||||
setSyncingVideo(false)
|
||||
})
|
||||
|
||||
hls.current.attachMedia(videoRef.current)
|
||||
|
||||
return () => {
|
||||
clearInterval(syncInterval)
|
||||
stopSyncInterval()
|
||||
}
|
||||
}, [])
|
||||
|
||||
|
@ -8,11 +8,15 @@ import PostsList from "@components/PostsList"
|
||||
|
||||
import PostService from "@models/post"
|
||||
|
||||
import useCenteredContainer from "@hooks/useCenteredContainer"
|
||||
|
||||
import "./index.less"
|
||||
|
||||
const PostPage = (props) => {
|
||||
const post_id = props.params.post_id
|
||||
|
||||
useCenteredContainer(true)
|
||||
|
||||
const [loading, result, error, repeat] = app.cores.api.useRequest(PostService.getPost, {
|
||||
post_id,
|
||||
})
|
||||
|
27
packages/app/src/pages/trending/[trending].jsx
Normal file
27
packages/app/src/pages/trending/[trending].jsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React from "react"
|
||||
|
||||
import PostList from "@components/PostsList"
|
||||
import PostModel from "@models/post"
|
||||
|
||||
import "./index.less"
|
||||
|
||||
const TrendingPage = (props) => {
|
||||
const { trending } = props.params
|
||||
|
||||
return <div className="trending-page">
|
||||
<div className="trending-page-header">
|
||||
<h1>#{trending.toLowerCase()}</h1>
|
||||
</div>
|
||||
|
||||
<div className="trending-page-content">
|
||||
<PostList
|
||||
loadFromModel={PostModel.getTrending}
|
||||
loadFromModelProps={{
|
||||
trending,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
|
||||
export default TrendingPage
|
18
packages/app/src/pages/trending/index.less
Normal file
18
packages/app/src/pages/trending/index.less
Normal file
@ -0,0 +1,18 @@
|
||||
.trending-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
gap: 20px;
|
||||
|
||||
width: 100%;
|
||||
|
||||
.trending-page-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.trending-page-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
@ -257,14 +257,14 @@ export default class StreamViewer extends React.Component {
|
||||
|
||||
enterPlayerAnimation = () => {
|
||||
app.cores.style.applyTemporalVariant("dark")
|
||||
app.cores.style.toggleCompactMode(true)
|
||||
app.layout.toggleCompactMode(true)
|
||||
app.layout.toggleCenteredContent(false)
|
||||
app.controls.toggleUIVisibility(false)
|
||||
}
|
||||
|
||||
exitPlayerAnimation = () => {
|
||||
app.cores.style.applyVariant(app.cores.style.currentVariantKey)
|
||||
app.cores.style.toggleCompactMode(false)
|
||||
app.layout.toggleCompactMode(false)
|
||||
app.layout.toggleCenteredContent(true)
|
||||
app.controls.toggleUIVisibility(true)
|
||||
}
|
||||
|
@ -14,29 +14,4 @@ export default [
|
||||
component: LivestreamsList,
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
key: "controlPanel",
|
||||
label: "Creator Panel",
|
||||
icon: "MdSpaceDashboard",
|
||||
children: [
|
||||
{
|
||||
key: "controlPanel.uploads",
|
||||
label: "Uploads",
|
||||
icon: "FiUpload",
|
||||
disabled: true
|
||||
},
|
||||
{
|
||||
key: "controlPanel.streaming_settings",
|
||||
label: "Stream Configuration",
|
||||
icon: "FiSettings",
|
||||
disabled: true,
|
||||
},
|
||||
{
|
||||
key: "controlPanel.dvr_settings",
|
||||
label: "DVR",
|
||||
icon: "MdFiberDvr",
|
||||
disabled: true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -7,55 +7,61 @@ import WidgetItemPreview from "@components/WidgetItemPreview"
|
||||
|
||||
import "./index.less"
|
||||
|
||||
export default class WidgetsManager extends React.Component {
|
||||
state = {
|
||||
loadedWidgets: app.cores.widgets.getInstalled() ?? [],
|
||||
}
|
||||
const WidgetsManager = () => {
|
||||
const [loadedWidgets, setLoadedWidgets] = React.useState(app.cores.widgets.getInstalled() ?? [])
|
||||
|
||||
render() {
|
||||
return <div className="widgets-manager">
|
||||
<div className="widgets-manager-list">
|
||||
{
|
||||
Array.isArray(this.state.loadedWidgets) && this.state.loadedWidgets.map((manifest) => {
|
||||
return <React.Fragment>
|
||||
<WidgetItemPreview
|
||||
manifest={manifest}
|
||||
onRemove={() => {
|
||||
app.cores.widgets.uninstall(manifest._id)
|
||||
}}
|
||||
onInstall={() => {
|
||||
app.cores.widgets.install(manifest._id)
|
||||
}}
|
||||
onUpdate={() => {
|
||||
app.cores.widgets.install(manifest._id, {
|
||||
update: true,
|
||||
})
|
||||
}}
|
||||
onChangeVisible={(visible) => {
|
||||
app.cores.widgets.toggleVisibility(manifest._id, visible)
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
})
|
||||
}
|
||||
React.useEffect(() => {
|
||||
if (app.layout.tools_bar) {
|
||||
app.layout.tools_bar.toggleVisibility(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
<div
|
||||
className="widget_load_list_item"
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
return <div className="widgets-manager">
|
||||
<h1>Widgets</h1>
|
||||
|
||||
<div className="widgets-manager-list">
|
||||
{
|
||||
Array.isArray(loadedWidgets) && loadedWidgets.map((manifest) => {
|
||||
return <React.Fragment>
|
||||
<WidgetItemPreview
|
||||
manifest={manifest}
|
||||
onRemove={() => {
|
||||
app.cores.widgets.uninstall(manifest._id)
|
||||
}}
|
||||
onInstall={() => {
|
||||
app.cores.widgets.install(manifest._id)
|
||||
}}
|
||||
onUpdate={() => {
|
||||
app.cores.widgets.install(manifest._id, {
|
||||
update: true,
|
||||
})
|
||||
}}
|
||||
onChangeVisible={(visible) => {
|
||||
app.cores.widgets.toggleVisibility(manifest._id, visible)
|
||||
}}
|
||||
/>
|
||||
</React.Fragment>
|
||||
})
|
||||
}
|
||||
|
||||
<div
|
||||
className="widget_load_list_item"
|
||||
style={{
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
}}
|
||||
>
|
||||
<antd.Button
|
||||
type="primary"
|
||||
icon={<Icons.FiPlus />}
|
||||
onClick={() => { }}
|
||||
>
|
||||
<antd.Button
|
||||
type="primary"
|
||||
icon={<Icons.FiPlus />}
|
||||
onClick={openWidgetsBrowserModal}
|
||||
>
|
||||
Install more
|
||||
</antd.Button>
|
||||
</div>
|
||||
Install more
|
||||
</antd.Button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default WidgetsManager
|
@ -1,5 +1,3 @@
|
||||
import React from "react"
|
||||
|
||||
import WidgetsManager from "../components/widgetsManager"
|
||||
|
||||
export default {
|
||||
@ -7,17 +5,5 @@ export default {
|
||||
icon: "FiList",
|
||||
label: "Widgets",
|
||||
group: "app",
|
||||
render: () => {
|
||||
React.useEffect(() => {
|
||||
if (app.layout.tools_bar) {
|
||||
app.layout.tools_bar.toggleVisibility(true)
|
||||
}
|
||||
}, [])
|
||||
|
||||
return <div>
|
||||
<h1>Widgets</h1>
|
||||
|
||||
<WidgetsManager />
|
||||
</div>
|
||||
},
|
||||
render: WidgetsManager
|
||||
}
|
@ -577,7 +577,8 @@
|
||||
}
|
||||
|
||||
// fix input
|
||||
.ant-input {
|
||||
.ant-input,
|
||||
.ant-mentions {
|
||||
background-color: var(--background-color-accent);
|
||||
|
||||
color: var(--text-color);
|
||||
@ -588,11 +589,37 @@
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper {
|
||||
background-color: var(--background-color-accent);
|
||||
.ant-mentions-outlined {
|
||||
background: transparent;
|
||||
|
||||
&:hover {
|
||||
background: transparent;
|
||||
background-color: var(--background-color-accent) !important;
|
||||
}
|
||||
|
||||
&:focus {
|
||||
background: transparent;
|
||||
background-color: var(--background-color-accent) !important;
|
||||
}
|
||||
|
||||
&:focus-within {
|
||||
background: transparent;
|
||||
background-color: var(--background-color-accent) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.ant-input-affix-wrapper,
|
||||
.ant-mentions-affix-wrapper {
|
||||
background: transparent;
|
||||
background-color: var(--background-color-accent);
|
||||
color: var(--text-color);
|
||||
|
||||
border: 0 !important;
|
||||
|
||||
:hover {
|
||||
border: 0 !important;
|
||||
}
|
||||
|
||||
::placeholder {
|
||||
color: var(--text-color);
|
||||
opacity: 0.5;
|
||||
|
@ -83,6 +83,8 @@ a {
|
||||
|
||||
transition: all 150ms ease-in-out;
|
||||
|
||||
overflow-x: hidden;
|
||||
|
||||
&.electron {
|
||||
.ant-layout-sider {
|
||||
padding-top: 0px;
|
||||
@ -167,6 +169,13 @@ html {
|
||||
// }
|
||||
// }
|
||||
|
||||
&.compact-mode {
|
||||
.content_layout {
|
||||
padding: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
}
|
||||
|
||||
&.centered-content {
|
||||
.content_layout {
|
||||
margin: 0 auto;
|
||||
|
@ -2,13 +2,33 @@ export default {
|
||||
name: "Extension",
|
||||
collection: "extensions",
|
||||
schema: {
|
||||
user_id: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
assetsUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
srcUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
registryId: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
packageUrl: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
version: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
title: {
|
||||
name: {
|
||||
type: String,
|
||||
default: "Untitled"
|
||||
default: "untitled"
|
||||
},
|
||||
description: {
|
||||
type: String,
|
||||
@ -22,9 +42,5 @@ export default {
|
||||
type: Date,
|
||||
required: true,
|
||||
},
|
||||
experimental: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
}
|
||||
}
|
@ -172,12 +172,14 @@ export default class Proxy {
|
||||
|
||||
if (!route) {
|
||||
res.statusCode = 404
|
||||
res.end(`
|
||||
{
|
||||
"error": "404 Not found"
|
||||
}
|
||||
`)
|
||||
return
|
||||
|
||||
res.end(JSON.stringify({
|
||||
error: "Gateway route not found",
|
||||
details: "The gateway route you are trying to access does not exist, maybe the service is down...",
|
||||
namespace: namespace,
|
||||
}))
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
if (route.pathRewrite) {
|
||||
|
@ -1,8 +1,6 @@
|
||||
import templates from "../templates"
|
||||
|
||||
export default async (ctx, data) => {
|
||||
console.log(`EMS Send account activation `, data)
|
||||
|
||||
const { user, activation_code } = data
|
||||
|
||||
const result = await ctx.mailTransporter.sendMail({
|
||||
|
@ -0,0 +1,3 @@
|
||||
export default class Extension {
|
||||
static resolve = require("./methods/resolve").default
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
import { Extension } from "@db_models"
|
||||
|
||||
export default async function resolve(payload) {
|
||||
let { user_id, pkg } = payload
|
||||
|
||||
const [pkgName, pkgVersion] = pkg.split("@")
|
||||
|
||||
if (!pkgVersion) {
|
||||
pkgVersion = "latest"
|
||||
}
|
||||
|
||||
if (pkgVersion === "latest") {
|
||||
return await Extension.findOne({
|
||||
user_id,
|
||||
name: pkgName,
|
||||
}).sort({ version: -1 }).limit(1).exec()
|
||||
}
|
||||
|
||||
return await Extension.findOne({
|
||||
user_id,
|
||||
name: pkgName,
|
||||
version: pkgVersion,
|
||||
})
|
||||
}
|
@ -1,6 +1,8 @@
|
||||
import { Server } from "linebridge"
|
||||
import B2 from "backblaze-b2"
|
||||
|
||||
import DbManager from "@shared-classes/DbManager"
|
||||
import CacheService from "@shared-classes/CacheService"
|
||||
|
||||
import SharedMiddlewares from "@shared-middlewares"
|
||||
|
||||
@ -16,10 +18,21 @@ class API extends Server {
|
||||
|
||||
contexts = {
|
||||
db: new DbManager(),
|
||||
b2: new B2({
|
||||
applicationKeyId: process.env.B2_KEY_ID,
|
||||
applicationKey: process.env.B2_APP_KEY,
|
||||
}),
|
||||
cache: new CacheService({
|
||||
fsram: false
|
||||
}),
|
||||
}
|
||||
|
||||
async onInitialize() {
|
||||
await this.contexts.db.initialize()
|
||||
await this.contexts.b2.authorize()
|
||||
|
||||
global.cache = this.contexts.cache
|
||||
global.b2 = this.contexts.b2
|
||||
}
|
||||
|
||||
handleWsAuth = require("@shared-lib/handleWsAuth").default
|
||||
|
@ -2,8 +2,9 @@
|
||||
"name": "marketplace",
|
||||
"version": "0.60.2",
|
||||
"dependencies": {
|
||||
"7zip-min": "^1.4.4",
|
||||
"@octokit/rest": "^19.0.7",
|
||||
"7zip-min": "^1.4.4",
|
||||
"backblaze-b2": "^1.7.0",
|
||||
"sucrase": "^3.32.0",
|
||||
"uglify-js": "^3.17.4"
|
||||
}
|
||||
|
@ -0,0 +1,10 @@
|
||||
import ExtensionClass from "@classes/extension"
|
||||
|
||||
export default async (req) => {
|
||||
const { user_id, pkg } = req.params
|
||||
|
||||
return await ExtensionClass.resolveManifest({
|
||||
user_id,
|
||||
pkg,
|
||||
})
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
import ExtensionClass from "@classes/extension"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { user_id, pkg } = req.params
|
||||
|
||||
const manifest = await ExtensionClass.resolve({
|
||||
user_id,
|
||||
pkg,
|
||||
})
|
||||
|
||||
return manifest
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
import { Extension } from "@db_models"
|
||||
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
import sevenzip from "7zip-min"
|
||||
|
||||
async function uploadFolderToB2(bucketId, folderPath, b2Directory) {
|
||||
try {
|
||||
const uploadFiles = async (dir) => {
|
||||
const files = fs.readdirSync(dir)
|
||||
|
||||
for (const file of files) {
|
||||
const fullPath = path.join(dir, file)
|
||||
const stats = fs.statSync(fullPath)
|
||||
|
||||
if (stats.isDirectory()) {
|
||||
await uploadFiles(fullPath)
|
||||
} else {
|
||||
const fileData = fs.readFileSync(fullPath)
|
||||
const b2FileName = path.join(b2Directory, path.relative(folderPath, fullPath)).replace(/\\/g, '/')
|
||||
|
||||
console.log(`Uploading ${b2FileName}...`)
|
||||
|
||||
const uploadUrl = await b2.getUploadUrl({
|
||||
bucketId: bucketId,
|
||||
})
|
||||
|
||||
await b2.uploadFile({
|
||||
uploadUrl: uploadUrl.data.uploadUrl,
|
||||
uploadAuthToken: uploadUrl.data.authorizationToken,
|
||||
fileName: b2FileName,
|
||||
data: fileData,
|
||||
})
|
||||
|
||||
console.log(`Uploaded ${b2FileName}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await uploadFiles(folderPath)
|
||||
console.log('All files uploaded successfully.')
|
||||
} catch (error) {
|
||||
console.error('Error uploading folder:', error)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
let { pkg } = req.headers
|
||||
|
||||
if (!pkg) {
|
||||
throw new OperationError(400, "Missing package")
|
||||
}
|
||||
|
||||
if (!req.auth) {
|
||||
throw new OperationError(401, "Unauthorized")
|
||||
}
|
||||
|
||||
pkg = JSON.parse(pkg)
|
||||
|
||||
const { user_id } = req.auth.session
|
||||
const registryId = `${user_id}/${pkg.name}@${pkg.version}`
|
||||
const s3Path = `${process.env.B2_BUCKET_ID}/extensions/${pkg.name}/${pkg.version}`
|
||||
const assetsUrl = `https://${process.env.B2_CDN_ENDPOINT}/${process.env.B2_BUCKET}/${s3Path}`
|
||||
|
||||
const workPath = path.resolve(global.cache.constructor.cachePath, String(Date.now()), registryId)
|
||||
const pkgPath = path.resolve(workPath, "pkg")
|
||||
const bundlePath = path.resolve(workPath, "bundle.7z")
|
||||
|
||||
// console.log({
|
||||
// user_id,
|
||||
// pkg,
|
||||
// registryId,
|
||||
// s3Path,
|
||||
// workPath,
|
||||
// bundlePath
|
||||
// })
|
||||
|
||||
let extensionRegistry = await Extension.findOne({
|
||||
user_id: user_id,
|
||||
registryId: registryId,
|
||||
version: pkg.version
|
||||
})
|
||||
|
||||
if (extensionRegistry) {
|
||||
throw new OperationError(400, "Extension already exists")
|
||||
}
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(workPath)) {
|
||||
await fs.promises.mkdir(workPath, { recursive: true })
|
||||
}
|
||||
|
||||
// read multipart form
|
||||
await req.multipart(async (field) => {
|
||||
await field.write(bundlePath)
|
||||
})
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
sevenzip.unpack(bundlePath, pkgPath, (error) => {
|
||||
if (error) {
|
||||
fs.promises.rm(workPath, { recursive: true, force: true })
|
||||
reject(error)
|
||||
} else {
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
await uploadFolderToB2(process.env.B2_BUCKET_ID, pkgPath, s3Path)
|
||||
|
||||
fs.promises.rm(workPath, { recursive: true, force: true })
|
||||
|
||||
|
||||
extensionRegistry = await Extension.create({
|
||||
user_id: user_id,
|
||||
name: pkg.name,
|
||||
version: pkg.version,
|
||||
registryId: registryId,
|
||||
assetsUrl: assetsUrl,
|
||||
srcUrl: `${assetsUrl}/src`,
|
||||
packageUrl: `${assetsUrl}/package.json`,
|
||||
created_at: Date.now(),
|
||||
})
|
||||
|
||||
return extensionRegistry
|
||||
} catch (error) {
|
||||
fs.promises.rm(workPath, { recursive: true, force: true })
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +0,0 @@
|
||||
import { Extension } from "@db_models"
|
||||
|
||||
export default {
|
||||
middlewares: ["withAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
|
||||
}
|
||||
}
|
@ -18,6 +18,13 @@ export default {
|
||||
throw new OperationError(403, "Unauthorized")
|
||||
}
|
||||
|
||||
console.log(`Setting lyrics for track ${track_id} >`, {
|
||||
track_id: track_id,
|
||||
video_source: video_source,
|
||||
lrc: lrc,
|
||||
track: track,
|
||||
})
|
||||
|
||||
let trackLyric = await TrackLyric.findOne({
|
||||
track_id: track_id
|
||||
}).lean()
|
||||
@ -35,10 +42,9 @@ export default {
|
||||
trackLyric.sync_audio_at = sync_audio_at
|
||||
}
|
||||
|
||||
trackLyric = await TrackLyric.findOneAndUpdate(
|
||||
{
|
||||
_id: trackLyric._id
|
||||
},
|
||||
trackLyric = await TrackLyric.findOneAndUpdate({
|
||||
track_id: track_id
|
||||
},
|
||||
trackLyric
|
||||
)
|
||||
} else {
|
||||
|
@ -0,0 +1,25 @@
|
||||
import { Post } from "@db_models"
|
||||
import fullfill from "@classes/posts/methods/fullfill"
|
||||
|
||||
export default {
|
||||
middlewares: ["withOptionalAuthentication"],
|
||||
fn: async (req) => {
|
||||
const { limit, trim } = req.query
|
||||
|
||||
let result = await Post.find({
|
||||
message: {
|
||||
$regex: new RegExp(`#${req.params.trending}`, "gi")
|
||||
}
|
||||
})
|
||||
.sort({ created_at: -1 })
|
||||
.skip(trim ?? 0)
|
||||
.limit(limit ?? 20)
|
||||
|
||||
result = await fullfill({
|
||||
posts: result,
|
||||
for_user_id: req.auth.session.user_id,
|
||||
})
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
@ -2,56 +2,44 @@ import { Post } from "@db_models"
|
||||
import { DateTime } from "luxon"
|
||||
|
||||
const maxDaysOld = 30
|
||||
const maxHashtags = 5
|
||||
|
||||
export default async (req) => {
|
||||
// fetch all posts that contain in message an #, with a maximun of 5 diferent hashtags
|
||||
let posts = await Post.find({
|
||||
message: {
|
||||
$regex: /#/gi
|
||||
},
|
||||
created_at: {
|
||||
$gte: DateTime.local().minus({ days: maxDaysOld }).toISO()
|
||||
}
|
||||
})
|
||||
.lean()
|
||||
const startDate = DateTime.local().minus({ days: maxDaysOld }).toISO()
|
||||
|
||||
// get the hastag content
|
||||
posts = posts.map((post) => {
|
||||
post.hashtags = post.message.match(/#[a-zA-Z0-9_]+/gi)
|
||||
|
||||
post.hashtags = post.hashtags.map((hashtag) => {
|
||||
return hashtag.substring(1)
|
||||
})
|
||||
|
||||
return post
|
||||
})
|
||||
|
||||
// build trendings
|
||||
let trendings = posts.reduce((acc, post) => {
|
||||
post.hashtags.forEach((hashtag) => {
|
||||
if (acc.find((trending) => trending.hashtag === hashtag)) {
|
||||
acc = acc.map((trending) => {
|
||||
if (trending.hashtag === hashtag) {
|
||||
trending.count++
|
||||
}
|
||||
|
||||
return trending
|
||||
})
|
||||
} else {
|
||||
acc.push({
|
||||
hashtag,
|
||||
count: 1
|
||||
})
|
||||
const trendings = await Post.aggregate([
|
||||
{
|
||||
$match: {
|
||||
message: { $regex: /#/gi },
|
||||
created_at: { $gte: startDate }
|
||||
}
|
||||
})
|
||||
},
|
||||
{
|
||||
$project: {
|
||||
hashtags: {
|
||||
$regexFindAll: {
|
||||
input: "$message",
|
||||
regex: /#[a-zA-Z0-9_]+/g
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{ $unwind: "$hashtags" },
|
||||
{
|
||||
$project: {
|
||||
hashtag: { $substr: ["$hashtags.match", 1, -1] }
|
||||
}
|
||||
},
|
||||
{
|
||||
$group: {
|
||||
_id: "$hashtag",
|
||||
count: { $sum: 1 }
|
||||
}
|
||||
},
|
||||
{ $sort: { count: -1 } },
|
||||
{ $limit: maxHashtags }
|
||||
])
|
||||
|
||||
return acc
|
||||
}, [])
|
||||
|
||||
// sort by count
|
||||
trendings = trendings.sort((a, b) => {
|
||||
return b.count - a.count
|
||||
})
|
||||
|
||||
return trendings
|
||||
return trendings.map(({ _id, count }) => ({ hashtag: _id, count }));
|
||||
}
|
44
packages/server/services/users/routes/users/search/get.js
Normal file
44
packages/server/services/users/routes/users/search/get.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { User } from "@db_models"
|
||||
|
||||
const ALLOWED_FIELDS = [
|
||||
"username",
|
||||
"publicName",
|
||||
"id",
|
||||
]
|
||||
|
||||
export default {
|
||||
middlewares: ["withOptionalAuthentication"],
|
||||
fn: async (req, res) => {
|
||||
const { keywords, limit = 50 } = req.query
|
||||
|
||||
let filters = {}
|
||||
|
||||
if (keywords) {
|
||||
keywords.split(";").forEach((pair) => {
|
||||
const [field, value] = pair.split(":")
|
||||
|
||||
if (value === "" || value === " ") {
|
||||
return
|
||||
}
|
||||
|
||||
// Verifica que el campo esté en los permitidos y que tenga un valor
|
||||
if (ALLOWED_FIELDS.includes(field) && value) {
|
||||
// Si el campo es "id", se busca coincidencia exacta
|
||||
if (field === "id") {
|
||||
filters[field] = value
|
||||
} else {
|
||||
// Para otros campos, usa $regex para coincidencias parciales
|
||||
filters[field] = { $regex: `\\b${value}`, $options: "i" }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
console.log(filters)
|
||||
|
||||
let users = await User.find(filters)
|
||||
.limit(limit)
|
||||
|
||||
return users
|
||||
}
|
||||
}
|
@ -1,12 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
import path from "path"
|
||||
import fs from "fs"
|
||||
import axios from "axios"
|
||||
import sevenzip from "7zip-min"
|
||||
import formdata from "form-data"
|
||||
|
||||
const tmpPath = path.join(process.cwd(), ".tmp")
|
||||
|
||||
const widgetsApi = "http://localhost:3040"
|
||||
const marketplaceAPIOrigin = "https://indev.comty.app/api/extensions"
|
||||
const token = process.argv[2]
|
||||
|
||||
const excludedFiles = [
|
||||
@ -17,9 +16,18 @@ const excludedFiles = [
|
||||
"/package-lock.json",
|
||||
]
|
||||
|
||||
async function copyToTmp(origin) {
|
||||
const rootPath = process.cwd()
|
||||
const tmpPath = path.join(rootPath, ".tmp")
|
||||
const buildPath = path.join(tmpPath, "build")
|
||||
const bundlePath = path.join(tmpPath, "bundle.7z")
|
||||
|
||||
async function copySources(origin, to) {
|
||||
const files = fs.readdirSync(origin)
|
||||
|
||||
if (!fs.existsSync(to)) {
|
||||
await fs.promises.mkdir(to, { recursive: true })
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = path.join(origin, file)
|
||||
|
||||
@ -33,14 +41,9 @@ async function copyToTmp(origin) {
|
||||
}
|
||||
|
||||
if (fs.lstatSync(filePath).isDirectory()) {
|
||||
await copyToTmp(filePath)
|
||||
await copySources(filePath, path.join(to, file))
|
||||
} else {
|
||||
const fileContent = fs.readFileSync(filePath)
|
||||
const relativePath = filePath.replace(process.cwd(), "")
|
||||
const tmpFilePath = path.join(tmpPath, relativePath)
|
||||
|
||||
fs.mkdirSync(path.dirname(tmpFilePath), { recursive: true })
|
||||
fs.writeFileSync(tmpFilePath, fileContent)
|
||||
await fs.promises.copyFile(filePath, path.join(to, file))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -63,50 +66,69 @@ async function main() {
|
||||
return
|
||||
}
|
||||
|
||||
const rootPath = process.cwd()
|
||||
|
||||
// create a .tmp folder
|
||||
if (!fs.existsSync(tmpPath)) {
|
||||
fs.mkdirSync(tmpPath)
|
||||
if (fs.existsSync(tmpPath)) {
|
||||
await fs.promises.rm(tmpPath, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
const bundlePath = path.join(rootPath, "bundle.7z")
|
||||
try {
|
||||
// try to read package.json
|
||||
if (!fs.existsSync(path.resolve(rootPath, "package.json"))) {
|
||||
console.error("🛑 package.json not found")
|
||||
return
|
||||
}
|
||||
|
||||
console.log("📦 Creating bundle...")
|
||||
const packageJSON = require(path.resolve(rootPath, "package.json"))
|
||||
|
||||
await copyToTmp(rootPath)
|
||||
// check if package.json has a main file
|
||||
if (!packageJSON.main) {
|
||||
console.error("🛑 package.json does not have a main file")
|
||||
return
|
||||
}
|
||||
|
||||
await createBundle(`${tmpPath}/*`, bundlePath)
|
||||
if (!fs.existsSync(path.resolve(rootPath, packageJSON.main))) {
|
||||
console.error("🛑 main file not found")
|
||||
return
|
||||
}
|
||||
|
||||
await fs.promises.rm(tmpPath, { recursive: true, force: true })
|
||||
console.log(packageJSON)
|
||||
|
||||
console.log("📦✅ Bundle created successfully")
|
||||
console.log("📦 Creating bundle...")
|
||||
|
||||
console.log("🚚 Publishing bundle...")
|
||||
await copySources(rootPath, buildPath)
|
||||
await createBundle(`${buildPath}/*`, bundlePath)
|
||||
|
||||
const formData = new formdata()
|
||||
console.log("📦✅ Bundle created successfully")
|
||||
|
||||
formData.append("bundle", fs.createReadStream(bundlePath))
|
||||
console.log("🚚 Publishing bundle...")
|
||||
|
||||
const response = await axios({
|
||||
method: "POST",
|
||||
url: `${widgetsApi}/widgets/publish`,
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
data: formData,
|
||||
}).catch((error) => {
|
||||
console.error("🛑 Error while publishing bundle \n\t", error.response?.data ?? error)
|
||||
const formData = new formdata()
|
||||
|
||||
return false
|
||||
})
|
||||
formData.append("file", fs.createReadStream(bundlePath))
|
||||
|
||||
await fs.promises.rm(bundlePath, { recursive: true, force: true })
|
||||
await fs.promises.rm(tmpPath, { recursive: true, force: true })
|
||||
const response = await axios({
|
||||
method: "PUT",
|
||||
url: `${marketplaceAPIOrigin}/publish`,
|
||||
headers: {
|
||||
...formData.getHeaders(),
|
||||
pkg: JSON.stringify(packageJSON),
|
||||
Authorization: `Bearer ${token}`,
|
||||
},
|
||||
data: formData,
|
||||
}).catch((error) => {
|
||||
console.error("🛑 Error while publishing bundle \n\t", error.response?.data ?? error)
|
||||
|
||||
if (response) {
|
||||
console.log("🚚✅ Bundle published successfully! \n", response.data)
|
||||
return false
|
||||
})
|
||||
|
||||
if (response) {
|
||||
console.log("🚚✅ Bundle published successfully! \n", response.data)
|
||||
}
|
||||
|
||||
await fs.promises.rm(tmpPath, { recursive: true, force: true })
|
||||
} catch (error) {
|
||||
console.error("🛑 Error while publishing bundle \n\t", error)
|
||||
await fs.promises.rm(tmpPath, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
|
||||
|
6
widgets/Clock/package.json
Normal file
6
widgets/Clock/package.json
Normal file
@ -0,0 +1,6 @@
|
||||
{
|
||||
"name": "clock",
|
||||
"version": "1.0.6",
|
||||
"description": "Display the current time",
|
||||
"main": "./src/extension.js"
|
||||
}
|
21
widgets/Clock/src/clock.jsx
Normal file
21
widgets/Clock/src/clock.jsx
Normal file
@ -0,0 +1,21 @@
|
||||
import React from "react"
|
||||
|
||||
import "./index.less"
|
||||
|
||||
const Clock = () => {
|
||||
const [time, setTime] = React.useState(new Date())
|
||||
|
||||
React.useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setTime(new Date())
|
||||
}, 1000)
|
||||
|
||||
return () => clearInterval(interval)
|
||||
}, [])
|
||||
|
||||
return <div className="clock">
|
||||
{time.toLocaleTimeString()}
|
||||
</div>
|
||||
}
|
||||
|
||||
export default Clock
|
45
widgets/Clock/src/extension.js
Normal file
45
widgets/Clock/src/extension.js
Normal file
@ -0,0 +1,45 @@
|
||||
export default class Clock {
|
||||
registerWidgets = [
|
||||
{
|
||||
name: "Clock",
|
||||
description: "Display the current time",
|
||||
component: () => import("./clock.jsx"),
|
||||
}
|
||||
]
|
||||
|
||||
registerPages = [
|
||||
{
|
||||
path: "/clock",
|
||||
component: () => import("./clock.jsx"),
|
||||
}
|
||||
]
|
||||
|
||||
public = {
|
||||
echo: (...str) => {
|
||||
this.console.log(...str)
|
||||
},
|
||||
fib: (n) => {
|
||||
let a = 0, b = 1
|
||||
for (let i = 0; i < n; i++) {
|
||||
let c = a + b
|
||||
a = b
|
||||
b = c
|
||||
}
|
||||
return a
|
||||
}
|
||||
}
|
||||
|
||||
events = {
|
||||
"test": (data) => {
|
||||
this.console.log("test")
|
||||
|
||||
if (data) {
|
||||
this.console.log(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async onInitialize() {
|
||||
this.console.log("Hi from the extension worker!")
|
||||
}
|
||||
}
|
6
widgets/Clock/src/index.less
Normal file
6
widgets/Clock/src/index.less
Normal file
@ -0,0 +1,6 @@
|
||||
.clock {
|
||||
font-size: 2rem;
|
||||
color: blue;
|
||||
|
||||
font-family: "DM Mono", monospace;
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user