Unfurl Focalboard Link (#1081)
This commit is contained in:
parent
8666bc833a
commit
eed1f86c15
17 changed files with 997 additions and 15 deletions
|
@ -7,7 +7,7 @@ replace github.com/mattermost/focalboard/server => ../server
|
||||||
require (
|
require (
|
||||||
github.com/mattermost/focalboard/server v0.0.0-20210525112228-f43e4028dbdc
|
github.com/mattermost/focalboard/server v0.0.0-20210525112228-f43e4028dbdc
|
||||||
github.com/mattermost/mattermost-plugin-api v0.0.20
|
github.com/mattermost/mattermost-plugin-api v0.0.20
|
||||||
github.com/mattermost/mattermost-server/v6 v6.0.0-20210913141218-bb659d03fde0
|
github.com/mattermost/mattermost-server/v6 v6.0.0-20211013145127-6521b0dfe3e5
|
||||||
github.com/pkg/errors v0.9.1
|
github.com/pkg/errors v0.9.1
|
||||||
github.com/stretchr/testify v1.7.0
|
github.com/stretchr/testify v1.7.0
|
||||||
)
|
)
|
||||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,9 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/mattermost/focalboard/server/auth"
|
"github.com/mattermost/focalboard/server/auth"
|
||||||
|
@ -19,9 +22,19 @@ import (
|
||||||
|
|
||||||
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
mmModel "github.com/mattermost/mattermost-server/v6/model"
|
||||||
"github.com/mattermost/mattermost-server/v6/plugin"
|
"github.com/mattermost/mattermost-server/v6/plugin"
|
||||||
|
"github.com/mattermost/mattermost-server/v6/shared/markdown"
|
||||||
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
"github.com/mattermost/mattermost-server/v6/shared/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type BoardsEmbed struct {
|
||||||
|
OriginalPath string `json:"originalPath"`
|
||||||
|
WorkspaceID string `json:"workspaceID"`
|
||||||
|
ViewID string `json:"viewID"`
|
||||||
|
BoardID string `json:"boardID"`
|
||||||
|
CardID string `json:"cardID"`
|
||||||
|
ReadToken string `json:"readToken,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes.
|
// Plugin implements the interface expected by the Mattermost server to communicate between the server and plugin processes.
|
||||||
type Plugin struct {
|
type Plugin struct {
|
||||||
plugin.MattermostPlugin
|
plugin.MattermostPlugin
|
||||||
|
@ -219,3 +232,130 @@ func defaultLoggingConfig() string {
|
||||||
}
|
}
|
||||||
}`
|
}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) MessageWillBePosted(_ *plugin.Context, post *mmModel.Post) (*mmModel.Post, string) {
|
||||||
|
return postWithBoardsEmbed(post, p.API.GetConfig().FeatureFlags.BoardsUnfurl), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) MessageWillBeUpdated(_ *plugin.Context, newPost, _ *mmModel.Post) (*mmModel.Post, string) {
|
||||||
|
return postWithBoardsEmbed(newPost, p.API.GetConfig().FeatureFlags.BoardsUnfurl), ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func postWithBoardsEmbed(post *mmModel.Post, showBoardsUnfurl bool) *mmModel.Post {
|
||||||
|
if _, ok := post.GetProps()["boards"]; ok {
|
||||||
|
post.AddProp("boards", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !showBoardsUnfurl {
|
||||||
|
return post
|
||||||
|
}
|
||||||
|
|
||||||
|
firstLink := getFirstLink(post.Message)
|
||||||
|
|
||||||
|
if firstLink == "" {
|
||||||
|
return post
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := url.Parse(firstLink)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return post
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trim away the first / because otherwise after we split the string, the first element in the array is a empty element
|
||||||
|
urlPath := u.Path
|
||||||
|
if strings.HasPrefix(urlPath, "/") {
|
||||||
|
urlPath = u.Path[1:]
|
||||||
|
}
|
||||||
|
pathSplit := strings.Split(strings.ToLower(urlPath), "/")
|
||||||
|
queryParams := u.Query()
|
||||||
|
|
||||||
|
if len(pathSplit) == 0 {
|
||||||
|
return post
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaceID, boardID, viewID, cardID := returnBoardsParams(pathSplit)
|
||||||
|
|
||||||
|
if workspaceID != "" && boardID != "" && viewID != "" && cardID != "" {
|
||||||
|
b, _ := json.Marshal(BoardsEmbed{
|
||||||
|
WorkspaceID: workspaceID,
|
||||||
|
BoardID: boardID,
|
||||||
|
ViewID: viewID,
|
||||||
|
CardID: cardID,
|
||||||
|
ReadToken: queryParams.Get("r"),
|
||||||
|
OriginalPath: u.RequestURI(),
|
||||||
|
})
|
||||||
|
|
||||||
|
BoardsPostEmbed := &mmModel.PostEmbed{
|
||||||
|
Type: mmModel.PostEmbedBoards,
|
||||||
|
Data: string(b),
|
||||||
|
}
|
||||||
|
|
||||||
|
if post.Metadata == nil {
|
||||||
|
post.Metadata = &mmModel.PostMetadata{}
|
||||||
|
}
|
||||||
|
|
||||||
|
post.Metadata.Embeds = []*mmModel.PostEmbed{BoardsPostEmbed}
|
||||||
|
post.AddProp("boards", string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
return post
|
||||||
|
}
|
||||||
|
|
||||||
|
func getFirstLink(str string) string {
|
||||||
|
firstLink := ""
|
||||||
|
|
||||||
|
markdown.Inspect(str, func(blockOrInline interface{}) bool {
|
||||||
|
if _, ok := blockOrInline.(*markdown.Autolink); ok {
|
||||||
|
if link := blockOrInline.(*markdown.Autolink).Destination(); firstLink == "" {
|
||||||
|
firstLink = link
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
return firstLink
|
||||||
|
}
|
||||||
|
|
||||||
|
func returnBoardsParams(pathArray []string) (workspaceID, boardID, viewID, cardID string) {
|
||||||
|
// The reason we are doing this search for the first instance of boards or plugins is to take into account URL subpaths
|
||||||
|
index := -1
|
||||||
|
for i := 0; i < len(pathArray); i++ {
|
||||||
|
if pathArray[i] == "boards" || pathArray[i] == "plugins" {
|
||||||
|
index = i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if index == -1 {
|
||||||
|
return workspaceID, boardID, viewID, cardID
|
||||||
|
}
|
||||||
|
|
||||||
|
// If at index, the parameter in the path is boards,
|
||||||
|
// then we've copied this directly as logged in user of that board
|
||||||
|
|
||||||
|
// If at index, the parameter in the path is plugins,
|
||||||
|
// then we've copied this from a shared board
|
||||||
|
|
||||||
|
// For card links copied on a non-shared board, the path looks like {...Mattermost Url}.../boards/workspace/workspaceID/boardID/viewID/cardID
|
||||||
|
|
||||||
|
// For card links copied on a shared board, the path looks like
|
||||||
|
// {...Mattermost Url}.../plugins/focalboard/workspace/workspaceID/shared/boardID/viewID/cardID?r=read_token
|
||||||
|
|
||||||
|
// This is a non-shared board card link
|
||||||
|
if len(pathArray)-index == 6 && pathArray[index] == "boards" && pathArray[index+1] == "workspace" {
|
||||||
|
workspaceID = pathArray[index+2]
|
||||||
|
boardID = pathArray[index+3]
|
||||||
|
viewID = pathArray[index+4]
|
||||||
|
cardID = pathArray[index+5]
|
||||||
|
} else if len(pathArray)-index == 8 && pathArray[index] == "plugins" &&
|
||||||
|
pathArray[index+1] == "focalboard" &&
|
||||||
|
pathArray[index+2] == "workspace" &&
|
||||||
|
pathArray[index+4] == "shared" { // This is a shared board card link
|
||||||
|
workspaceID = pathArray[index+3]
|
||||||
|
boardID = pathArray[index+5]
|
||||||
|
viewID = pathArray[index+6]
|
||||||
|
cardID = pathArray[index+7]
|
||||||
|
}
|
||||||
|
return workspaceID, boardID, viewID, cardID
|
||||||
|
}
|
||||||
|
|
|
@ -5,7 +5,8 @@ function blockList(line) {
|
||||||
return line.startsWith('.focalboard-body') ||
|
return line.startsWith('.focalboard-body') ||
|
||||||
line.startsWith('.GlobalHeaderComponent') ||
|
line.startsWith('.GlobalHeaderComponent') ||
|
||||||
line.startsWith('.boards-rhs-icon') ||
|
line.startsWith('.boards-rhs-icon') ||
|
||||||
line.startsWith('.focalboard-plugin-root');
|
line.startsWith('.focalboard-plugin-root') ||
|
||||||
|
line.startsWith('.FocalboardUnfurl');
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = function loader(source) {
|
module.exports = function loader(source) {
|
||||||
|
|
17
mattermost-plugin/webapp/package-lock.json
generated
17
mattermost-plugin/webapp/package-lock.json
generated
|
@ -6,6 +6,7 @@
|
||||||
"": {
|
"": {
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "3.12.1",
|
"core-js": "3.12.1",
|
||||||
|
"marked": ">=2.0.1",
|
||||||
"mattermost-redux": "5.33.1",
|
"mattermost-redux": "5.33.1",
|
||||||
"react-intl": "^5.13.5",
|
"react-intl": "^5.13.5",
|
||||||
"react-router-dom": "5.2.0"
|
"react-router-dom": "5.2.0"
|
||||||
|
@ -12235,6 +12236,17 @@
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/marked": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-a1hY8eqdP9JgmsaO0MYYhO9Li2nfY/5pAj+gWU5r41Lze6AV4Xty1cseLWDcOYimJnaVfQAomaA6NK+z2IyR+w==",
|
||||||
|
"bin": {
|
||||||
|
"marked": "bin/marked"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 12"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/mattermost-redux": {
|
"node_modules/mattermost-redux": {
|
||||||
"version": "5.33.1",
|
"version": "5.33.1",
|
||||||
"resolved": "https://registry.npmjs.org/mattermost-redux/-/mattermost-redux-5.33.1.tgz",
|
"resolved": "https://registry.npmjs.org/mattermost-redux/-/mattermost-redux-5.33.1.tgz",
|
||||||
|
@ -27341,6 +27353,11 @@
|
||||||
"object-visit": "^1.0.0"
|
"object-visit": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"marked": {
|
||||||
|
"version": "3.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/marked/-/marked-3.0.6.tgz",
|
||||||
|
"integrity": "sha512-a1hY8eqdP9JgmsaO0MYYhO9Li2nfY/5pAj+gWU5r41Lze6AV4Xty1cseLWDcOYimJnaVfQAomaA6NK+z2IyR+w=="
|
||||||
|
},
|
||||||
"mattermost-redux": {
|
"mattermost-redux": {
|
||||||
"version": "5.33.1",
|
"version": "5.33.1",
|
||||||
"resolved": "https://registry.npmjs.org/mattermost-redux/-/mattermost-redux-5.33.1.tgz",
|
"resolved": "https://registry.npmjs.org/mattermost-redux/-/mattermost-redux-5.33.1.tgz",
|
||||||
|
|
|
@ -75,6 +75,7 @@
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"core-js": "3.12.1",
|
"core-js": "3.12.1",
|
||||||
|
"marked": ">=2.0.1",
|
||||||
"mattermost-redux": "5.33.1",
|
"mattermost-redux": "5.33.1",
|
||||||
"react-intl": "^5.13.5",
|
"react-intl": "^5.13.5",
|
||||||
"react-router-dom": "5.2.0"
|
"react-router-dom": "5.2.0"
|
||||||
|
|
|
@ -0,0 +1,131 @@
|
||||||
|
.FocalboardUnfurl {
|
||||||
|
display: block;
|
||||||
|
padding: 16px;
|
||||||
|
border: 1px solid rgba(61, 60, 64, 0.24) !important;
|
||||||
|
border-radius: 4px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.08);
|
||||||
|
width: 425px;
|
||||||
|
text-decoration: none !important;
|
||||||
|
color: inherit !important;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
font-size: 36px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.information {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin-left: 10px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.card_title {
|
||||||
|
color: #3D3C40;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
max-width: 100%;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.board_title {
|
||||||
|
color: rgba(61, 60, 64, 0.56);
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.body {
|
||||||
|
border: 1px solid rgba(61, 60, 64, 0.16);
|
||||||
|
border-radius: 4px;
|
||||||
|
margin-top: 16px;
|
||||||
|
height: 145px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 16px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 40px;
|
||||||
|
margin-top: 16px;
|
||||||
|
|
||||||
|
.timestamp_properties {
|
||||||
|
margin-left: 12px;
|
||||||
|
max-width: 90%;
|
||||||
|
|
||||||
|
.properties {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
.remainder {
|
||||||
|
color: rgba(61, 60, 64, 0.48);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.property {
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
margin-right: 10px;
|
||||||
|
max-width: 33%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow-wrap: normal;
|
||||||
|
|
||||||
|
&.propColorDefault {
|
||||||
|
background-color: var(--prop-default);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.propColorGray {
|
||||||
|
background-color: var(--prop-gray);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.propColorBrown {
|
||||||
|
background-color: var(--prop-brown);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.propColorOrange {
|
||||||
|
background-color: var(--prop-orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.propColorYellow {
|
||||||
|
background-color: var(--prop-yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.propColorGreen {
|
||||||
|
background-color: var(--prop-green);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.propColorBlue {
|
||||||
|
background-color: var(--prop-blue);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.propColorPurple {
|
||||||
|
background-color: var(--prop-purple);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.propColorPink {
|
||||||
|
background-color: var(--prop-pink);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.propColorRed {
|
||||||
|
background-color: var(--prop-red);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
import React, {useState, useEffect} from 'react'
|
||||||
|
import {IntlProvider, FormattedMessage} from 'react-intl'
|
||||||
|
import {connect} from 'react-redux'
|
||||||
|
|
||||||
|
import {GlobalState} from 'mattermost-redux/types/store'
|
||||||
|
import {getCurrentUserLocale} from 'mattermost-redux/selectors/entities/i18n'
|
||||||
|
|
||||||
|
import {getMessages} from './../../../../../webapp/src/i18n'
|
||||||
|
import {Utils} from './../../../../../webapp/src/utils'
|
||||||
|
import {Card} from './../../../../../webapp/src/blocks/card'
|
||||||
|
import {Board} from './../../../../../webapp/src/blocks/board'
|
||||||
|
import {ContentBlock} from './../../../../../webapp/src/blocks/contentBlock'
|
||||||
|
import octoClient from './../../../../../webapp/src/octoClient'
|
||||||
|
|
||||||
|
const Avatar = (window as any).Components.Avatar
|
||||||
|
const Timestamp = (window as any).Components.Timestamp
|
||||||
|
const imageURLForUser = (window as any).Components.imageURLForUser
|
||||||
|
|
||||||
|
import './boardsUnfurl.scss'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
embed: {
|
||||||
|
data: string,
|
||||||
|
},
|
||||||
|
locale: string,
|
||||||
|
}
|
||||||
|
|
||||||
|
function mapStateToProps(state: GlobalState) {
|
||||||
|
const locale = getCurrentUserLocale(state)
|
||||||
|
|
||||||
|
return {
|
||||||
|
locale,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const BoardsUnfurl = (props: Props): JSX.Element => {
|
||||||
|
if (!props.embed || !props.embed.data) {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
const {embed, locale} = props
|
||||||
|
const focalboardInformation = JSON.parse(embed.data)
|
||||||
|
const {workspaceID, cardID, boardID, readToken, originalPath} = focalboardInformation
|
||||||
|
const baseURL = window.location.origin
|
||||||
|
|
||||||
|
if (!workspaceID || !cardID || !boardID) {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
const [card, setCard] = useState<Card>()
|
||||||
|
const [content, setContent] = useState<ContentBlock>()
|
||||||
|
const [board, setBoard] = useState<Board>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchData = async () => {
|
||||||
|
const [cards, boards] = await Promise.all(
|
||||||
|
[
|
||||||
|
octoClient.getBlocksWithBlockID(cardID, workspaceID, readToken),
|
||||||
|
octoClient.getBlocksWithBlockID(boardID, workspaceID, readToken),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
const [firstCard] = cards as Card[]
|
||||||
|
const [firstBoard] = boards as Board[]
|
||||||
|
if (!firstCard || !firstBoard) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
setCard(firstCard)
|
||||||
|
setBoard(firstBoard)
|
||||||
|
|
||||||
|
if (firstCard.fields.contentOrder.length) {
|
||||||
|
let [firstContentBlockID] = firstCard.fields?.contentOrder
|
||||||
|
|
||||||
|
if (Array.isArray(firstContentBlockID)) {
|
||||||
|
[firstContentBlockID] = firstContentBlockID
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentBlock = await octoClient.getBlocksWithBlockID(firstContentBlockID, workspaceID, readToken) as ContentBlock[]
|
||||||
|
const [firstContentBlock] = contentBlock
|
||||||
|
if (!firstContentBlock) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
setContent(firstContentBlock)
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
fetchData()
|
||||||
|
}, [originalPath])
|
||||||
|
|
||||||
|
if (!card || !board) {
|
||||||
|
return <></>
|
||||||
|
}
|
||||||
|
|
||||||
|
const propertyKeyArray = Object.keys(card.fields.properties)
|
||||||
|
const propertyValueArray = Object.values(card.fields.properties)
|
||||||
|
const options = board.fields.cardProperties
|
||||||
|
const propertiesToDisplay: Array<Record<string, string>> = []
|
||||||
|
|
||||||
|
// We will just display the first 3 or less select/multi-select properties and do a +n for remainder if any remainder
|
||||||
|
if (propertyKeyArray.length > 0) {
|
||||||
|
for (let i = 0; i < propertyKeyArray.length && propertiesToDisplay.length < 3; i++) {
|
||||||
|
const keyToLookUp = propertyKeyArray[i]
|
||||||
|
const correspondingOption = options.find((option) => option.id === keyToLookUp)
|
||||||
|
|
||||||
|
if (!correspondingOption) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
let valueToLookUp = propertyValueArray[i]
|
||||||
|
if (Array.isArray(valueToLookUp)) {
|
||||||
|
valueToLookUp = valueToLookUp[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
const optionSelected = correspondingOption.options.find((option) => option.id === valueToLookUp)
|
||||||
|
|
||||||
|
if (!optionSelected) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
propertiesToDisplay.push({optionName: correspondingOption.name, optionValue: optionSelected.value, optionValueColour: optionSelected.color})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const remainder = propertyKeyArray.length - propertiesToDisplay.length
|
||||||
|
const html: string = Utils.htmlFromMarkdown(content?.title || '')
|
||||||
|
return (
|
||||||
|
<IntlProvider
|
||||||
|
messages={getMessages(locale)}
|
||||||
|
locale={locale}
|
||||||
|
>
|
||||||
|
<a
|
||||||
|
className='FocalboardUnfurl'
|
||||||
|
href={`${baseURL}${originalPath}`}
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
target='_blank'
|
||||||
|
>
|
||||||
|
|
||||||
|
{/* Header of the Card*/}
|
||||||
|
<div className='header'>
|
||||||
|
<span className='icon'>{card.fields?.icon}</span>
|
||||||
|
<div className='information'>
|
||||||
|
<span className='card_title'>{card.title}</span>
|
||||||
|
<span className='board_title'>{board.title}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Body of the Card*/}
|
||||||
|
{html !== '' &&
|
||||||
|
<div className='body'>
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{__html: html}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
|
||||||
|
{/* Footer of the Card*/}
|
||||||
|
<div className='footer'>
|
||||||
|
<div className='avatar'>
|
||||||
|
<Avatar
|
||||||
|
size={'md'}
|
||||||
|
url={imageURLForUser(card.createdBy)}
|
||||||
|
className={'avatar-post-preview'}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className='timestamp_properties'>
|
||||||
|
<div className='properties'>
|
||||||
|
{propertiesToDisplay.map((property) => (
|
||||||
|
<div
|
||||||
|
key={property.optionValue}
|
||||||
|
className={`property ${property.optionValueColour}`}
|
||||||
|
title={`${property.optionName}`}
|
||||||
|
>
|
||||||
|
{property.optionValue}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
{remainder > 0 &&
|
||||||
|
<span className='remainder'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='BoardsUnfurl.Remainder'
|
||||||
|
defaultMessage='+{remainder} more'
|
||||||
|
values={{
|
||||||
|
remainder,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
<span className='post-preview__time'>
|
||||||
|
<FormattedMessage
|
||||||
|
id='BoardsUnfurl.Updated'
|
||||||
|
defaultMessage='Updated {time}'
|
||||||
|
values={{
|
||||||
|
time: (
|
||||||
|
<Timestamp
|
||||||
|
value={card.updateAt}
|
||||||
|
units={[
|
||||||
|
'now',
|
||||||
|
'minute',
|
||||||
|
'hour',
|
||||||
|
'day',
|
||||||
|
]}
|
||||||
|
useTime={false}
|
||||||
|
day={'numeric'}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a>
|
||||||
|
</IntlProvider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default connect(mapStateToProps)(BoardsUnfurl)
|
|
@ -13,6 +13,8 @@ windowAny.baseURL = '/plugins/focalboard'
|
||||||
windowAny.frontendBaseURL = '/boards'
|
windowAny.frontendBaseURL = '/boards'
|
||||||
windowAny.isFocalboardPlugin = true
|
windowAny.isFocalboardPlugin = true
|
||||||
|
|
||||||
|
import {ClientConfig} from 'mattermost-redux/types/config'
|
||||||
|
|
||||||
import App from '../../../webapp/src/app'
|
import App from '../../../webapp/src/app'
|
||||||
import store from '../../../webapp/src/store'
|
import store from '../../../webapp/src/store'
|
||||||
import GlobalHeader from '../../../webapp/src/components/globalHeader/globalHeader'
|
import GlobalHeader from '../../../webapp/src/components/globalHeader/globalHeader'
|
||||||
|
@ -26,6 +28,7 @@ import '../../../webapp/src/styles/main.scss'
|
||||||
import '../../../webapp/src/styles/labels.scss'
|
import '../../../webapp/src/styles/labels.scss'
|
||||||
import octoClient from '../../../webapp/src/octoClient'
|
import octoClient from '../../../webapp/src/octoClient'
|
||||||
|
|
||||||
|
import BoardsUnfurl from './components/boardsUnfurl/boardsUnfurl'
|
||||||
import wsClient, {MMWebSocketClient, ACTION_UPDATE_BLOCK, ACTION_UPDATE_CLIENT_CONFIG} from './../../../webapp/src/wsclient'
|
import wsClient, {MMWebSocketClient, ACTION_UPDATE_BLOCK, ACTION_UPDATE_CLIENT_CONFIG} from './../../../webapp/src/wsclient'
|
||||||
|
|
||||||
import manifest from './manifest'
|
import manifest from './manifest'
|
||||||
|
@ -143,6 +146,10 @@ export default class Plugin {
|
||||||
}
|
}
|
||||||
this.channelHeaderButtonId = registry.registerChannelHeaderButtonAction(<FocalboardIcon/>, goToFocalboardWorkspace, '', 'Boards')
|
this.channelHeaderButtonId = registry.registerChannelHeaderButtonAction(<FocalboardIcon/>, goToFocalboardWorkspace, '', 'Boards')
|
||||||
this.registry.registerProduct('/boards', 'product-boards', 'Boards', '/boards/welcome', MainApp, HeaderComponent)
|
this.registry.registerProduct('/boards', 'product-boards', 'Boards', '/boards/welcome', MainApp, HeaderComponent)
|
||||||
|
|
||||||
|
if (mmStore.getState().entities.general.config?.['FeatureFlagBoardsUnfurl' as keyof Partial<ClientConfig>] === 'true') {
|
||||||
|
this.registry.registerPostWillRenderEmbedComponent((embed) => embed.type === 'boards', BoardsUnfurl, false)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
windowAny.frontendBaseURL = subpath + '/plug/focalboard'
|
windowAny.frontendBaseURL = subpath + '/plug/focalboard'
|
||||||
this.channelHeaderButtonId = registry.registerChannelHeaderButtonAction(<FocalboardIcon/>, () => {
|
this.channelHeaderButtonId = registry.registerChannelHeaderButtonAction(<FocalboardIcon/>, () => {
|
||||||
|
|
|
@ -7,6 +7,7 @@ export interface PluginRegistry {
|
||||||
registerProductRoute(route: string, component: React.ElementType)
|
registerProductRoute(route: string, component: React.ElementType)
|
||||||
unregisterComponent(componentId: string)
|
unregisterComponent(componentId: string)
|
||||||
registerProduct(baseURL: string, switcherIcon: string, switcherText: string, switcherLinkURL: string, mainComponent: React.ElementType, headerCompoent: React.ElementType)
|
registerProduct(baseURL: string, switcherIcon: string, switcherText: string, switcherLinkURL: string, mainComponent: React.ElementType, headerCompoent: React.ElementType)
|
||||||
|
registerPostWillRenderEmbedComponent(match: (embed: {type: string, data: any}) => void, component: any, toggleable: boolean)
|
||||||
registerWebSocketEventHandler(event: string, handler: (e: any) => void)
|
registerWebSocketEventHandler(event: string, handler: (e: any) => void)
|
||||||
unregisterWebSocketEventHandler(event: string)
|
unregisterWebSocketEventHandler(event: string)
|
||||||
|
|
||||||
|
|
|
@ -1 +1,3 @@
|
||||||
declare module "mm-react-router-dom"
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
declare module 'mm-react-router-dom'
|
||||||
|
|
|
@ -2,10 +2,10 @@
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
const exec = require('child_process').exec;
|
const exec = require('child_process').exec;
|
||||||
|
|
||||||
const webpack = require('webpack');
|
|
||||||
|
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
|
const webpack = require('webpack');
|
||||||
|
|
||||||
const tsTransformer = require('@formatjs/ts-transformer');
|
const tsTransformer = require('@formatjs/ts-transformer');
|
||||||
|
|
||||||
const PLUGIN_ID = require('../plugin.json').id;
|
const PLUGIN_ID = require('../plugin.json').id;
|
||||||
|
|
|
@ -247,7 +247,8 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
|
||||||
parentID := query.Get("parent_id")
|
parentID := query.Get("parent_id")
|
||||||
blockType := query.Get("type")
|
blockType := query.Get("type")
|
||||||
all := query.Get("all")
|
all := query.Get("all")
|
||||||
container, err := a.getContainer(r)
|
blockID := query.Get("block_id")
|
||||||
|
container, err := a.getContainerAllowingReadTokenForBlock(r, blockID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.noContainerErrorResponse(w, r.URL.Path, err)
|
a.noContainerErrorResponse(w, r.URL.Path, err)
|
||||||
return
|
return
|
||||||
|
@ -258,15 +259,27 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
|
||||||
auditRec.AddMeta("parentID", parentID)
|
auditRec.AddMeta("parentID", parentID)
|
||||||
auditRec.AddMeta("blockType", blockType)
|
auditRec.AddMeta("blockType", blockType)
|
||||||
auditRec.AddMeta("all", all)
|
auditRec.AddMeta("all", all)
|
||||||
|
auditRec.AddMeta("blockID", blockID)
|
||||||
|
|
||||||
var blocks []model.Block
|
var blocks []model.Block
|
||||||
if all != "" {
|
var block *model.Block
|
||||||
|
switch {
|
||||||
|
case all != "":
|
||||||
blocks, err = a.app.GetAllBlocks(*container)
|
blocks, err = a.app.GetAllBlocks(*container)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else {
|
case blockID != "":
|
||||||
|
block, err = a.app.GetBlockWithID(*container, blockID)
|
||||||
|
if err != nil {
|
||||||
|
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if block != nil {
|
||||||
|
blocks = append(blocks, *block)
|
||||||
|
}
|
||||||
|
default:
|
||||||
blocks, err = a.app.GetBlocks(*container, parentID, blockType)
|
blocks, err = a.app.GetBlocks(*container, parentID, blockType)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
a.errorResponse(w, r.URL.Path, http.StatusInternalServerError, "", err)
|
||||||
|
@ -277,6 +290,7 @@ func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) {
|
||||||
a.logger.Debug("GetBlocks",
|
a.logger.Debug("GetBlocks",
|
||||||
mlog.String("parentID", parentID),
|
mlog.String("parentID", parentID),
|
||||||
mlog.String("blockType", blockType),
|
mlog.String("blockType", blockType),
|
||||||
|
mlog.String("blockID", blockID),
|
||||||
mlog.Int("block_count", len(blocks)),
|
mlog.Int("block_count", len(blocks)),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,10 @@ func (a *App) GetBlocks(c store.Container, parentID string, blockType string) ([
|
||||||
return a.store.GetBlocksWithParent(c, parentID)
|
return a.store.GetBlocksWithParent(c, parentID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) GetBlockWithID(c store.Container, blockID string) (*model.Block, error) {
|
||||||
|
return a.store.GetBlock(c, blockID)
|
||||||
|
}
|
||||||
|
|
||||||
func (a *App) GetBlocksWithRootID(c store.Container, rootID string) ([]model.Block, error) {
|
func (a *App) GetBlocksWithRootID(c store.Container, rootID string) ([]model.Block, error) {
|
||||||
return a.store.GetBlocksWithRootID(c, rootID)
|
return a.store.GetBlocksWithRootID(c, rootID)
|
||||||
}
|
}
|
||||||
|
@ -101,6 +105,7 @@ func (a *App) GetSubTree(c store.Container, blockID string, levels int) ([]model
|
||||||
if levels >= 3 {
|
if levels >= 3 {
|
||||||
return a.store.GetSubTree3(c, blockID)
|
return a.store.GetSubTree3(c, blockID)
|
||||||
}
|
}
|
||||||
|
|
||||||
return a.store.GetSubTree2(c, blockID)
|
return a.store.GetSubTree2(c, blockID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
"BoardComponent.no-property-title": "Items with an empty {property} property will go here. This column cannot be removed.",
|
"BoardComponent.no-property-title": "Items with an empty {property} property will go here. This column cannot be removed.",
|
||||||
"BoardComponent.show": "Show",
|
"BoardComponent.show": "Show",
|
||||||
"BoardPage.syncFailed": "Board may be deleted or access revoked.",
|
"BoardPage.syncFailed": "Board may be deleted or access revoked.",
|
||||||
|
"BoardsUnfurl.Remainder": "+{remainder} more",
|
||||||
|
"BoardsUnfurl.Updated": "Updated {time}",
|
||||||
"CardDetail.add-content": "Add content",
|
"CardDetail.add-content": "Add content",
|
||||||
"CardDetail.add-icon": "Add icon",
|
"CardDetail.add-icon": "Add icon",
|
||||||
"CardDetail.add-property": "+ Add a property",
|
"CardDetail.add-property": "+ Add a property",
|
||||||
|
@ -21,6 +23,8 @@
|
||||||
"CardDialog.nocard": "This card doesn't exist or is inaccessible.",
|
"CardDialog.nocard": "This card doesn't exist or is inaccessible.",
|
||||||
"CardDialog.copiedLink": "Copied!",
|
"CardDialog.copiedLink": "Copied!",
|
||||||
"CardDialog.copyLink": "Copy link",
|
"CardDialog.copyLink": "Copy link",
|
||||||
|
"CardDialog.editing-template": "You're editing a template.",
|
||||||
|
"CardDialog.nocard": "This card doesn't exist or is inaccessible.",
|
||||||
"ColorOption.selectColor": "Select {color} Color",
|
"ColorOption.selectColor": "Select {color} Color",
|
||||||
"Comment.delete": "Delete",
|
"Comment.delete": "Delete",
|
||||||
"CommentsList.send": "Send",
|
"CommentsList.send": "Send",
|
||||||
|
@ -46,7 +50,14 @@
|
||||||
"Dialog.closeDialog": "Close dialog",
|
"Dialog.closeDialog": "Close dialog",
|
||||||
"EditableDayPicker.today": "Today",
|
"EditableDayPicker.today": "Today",
|
||||||
"EmptyCenterPanel.no-content": "Add or select a board from the sidebar to get started.",
|
"EmptyCenterPanel.no-content": "Add or select a board from the sidebar to get started.",
|
||||||
"EmptyCenterPanel.workspace": "This is the workspace for:",
|
"EmptyCenterPanel.plugin.choose-a-template": "Choose a template",
|
||||||
|
"EmptyCenterPanel.plugin.empty-board": "Start with an Empty Board",
|
||||||
|
"EmptyCenterPanel.plugin.end-message": "You can change the channel using the switcher in the sidebar.",
|
||||||
|
"EmptyCenterPanel.plugin.new-template": "New template",
|
||||||
|
"EmptyCenterPanel.plugin.no-content-description": "Add a board to the sidebar using any of the templates defined below or start from scratch.{lineBreak} Members of \"{workspaceName}\" will have access to boards created here.",
|
||||||
|
"EmptyCenterPanel.plugin.no-content-or": "or",
|
||||||
|
"EmptyCenterPanel.plugin.no-content-title": "Create a Board in {workspaceName}",
|
||||||
|
"Error.mobileweb": "Mobile web support is currently in early beta. Not all functionality may be present.",
|
||||||
"Error.websocket-closed": "Websocket connection closed, connection interrupted. If this persists, check your server or web proxy configuration.",
|
"Error.websocket-closed": "Websocket connection closed, connection interrupted. If this persists, check your server or web proxy configuration.",
|
||||||
"Filter.includes": "includes",
|
"Filter.includes": "includes",
|
||||||
"Filter.is-empty": "is empty",
|
"Filter.is-empty": "is empty",
|
||||||
|
@ -54,17 +65,17 @@
|
||||||
"Filter.not-includes": "doesn't include",
|
"Filter.not-includes": "doesn't include",
|
||||||
"FilterComponent.add-filter": "+ Add filter",
|
"FilterComponent.add-filter": "+ Add filter",
|
||||||
"FilterComponent.delete": "Delete",
|
"FilterComponent.delete": "Delete",
|
||||||
"GalleryCard.delete": "Delete",
|
|
||||||
"GalleryCard.duplicate": "Duplicate",
|
|
||||||
"GalleryCard.copiedLink": "Copied!",
|
"GalleryCard.copiedLink": "Copied!",
|
||||||
"GalleryCard.copyLink": "Copy link",
|
"GalleryCard.copyLink": "Copy link",
|
||||||
"GroupBy.ungroup": "Ungroup",
|
"GalleryCard.delete": "Delete",
|
||||||
|
"GalleryCard.duplicate": "Duplicate",
|
||||||
"General.BoardCount": "{count, plural, one {# Board} other {# Boards}}",
|
"General.BoardCount": "{count, plural, one {# Board} other {# Boards}}",
|
||||||
|
"GroupBy.ungroup": "Ungroup",
|
||||||
|
"KanbanCard.copiedLink": "Copied!",
|
||||||
|
"KanbanCard.copyLink": "Copy link",
|
||||||
"KanbanCard.delete": "Delete",
|
"KanbanCard.delete": "Delete",
|
||||||
"KanbanCard.duplicate": "Duplicate",
|
"KanbanCard.duplicate": "Duplicate",
|
||||||
"KanbanCard.untitled": "Untitled",
|
"KanbanCard.untitled": "Untitled",
|
||||||
"KanbanCard.copiedLink": "Copied!",
|
|
||||||
"KanbanCard.copyLink": "Copy link",
|
|
||||||
"Mutator.duplicate-board": "duplicate board",
|
"Mutator.duplicate-board": "duplicate board",
|
||||||
"Mutator.new-board-from-template": "new board from template",
|
"Mutator.new-board-from-template": "new board from template",
|
||||||
"Mutator.new-card-from-template": "new card from template",
|
"Mutator.new-card-from-template": "new card from template",
|
||||||
|
@ -88,6 +99,7 @@
|
||||||
"PropertyType.URL": "URL",
|
"PropertyType.URL": "URL",
|
||||||
"PropertyType.UpdatedBy": "Last updated by",
|
"PropertyType.UpdatedBy": "Last updated by",
|
||||||
"PropertyType.UpdatedTime": "Last updated time",
|
"PropertyType.UpdatedTime": "Last updated time",
|
||||||
|
"PropertyValueElement.empty": "Empty",
|
||||||
"RegistrationLink.confirmRegenerateToken": "This will invalidate previously shared links. Continue?",
|
"RegistrationLink.confirmRegenerateToken": "This will invalidate previously shared links. Continue?",
|
||||||
"RegistrationLink.copiedLink": "Copied!",
|
"RegistrationLink.copiedLink": "Copied!",
|
||||||
"RegistrationLink.copyLink": "Copy link",
|
"RegistrationLink.copyLink": "Copy link",
|
||||||
|
@ -146,6 +158,7 @@
|
||||||
"View.NewBoardTitle": "Board view",
|
"View.NewBoardTitle": "Board view",
|
||||||
"View.NewGalleryTitle": "Gallery view",
|
"View.NewGalleryTitle": "Gallery view",
|
||||||
"View.NewTableTitle": "Table view",
|
"View.NewTableTitle": "Table view",
|
||||||
|
"View.NewTemplateTitle": "Untitled Template",
|
||||||
"View.Table": "Table",
|
"View.Table": "Table",
|
||||||
"ViewHeader.add-template": "New template",
|
"ViewHeader.add-template": "New template",
|
||||||
"ViewHeader.delete-template": "Delete",
|
"ViewHeader.delete-template": "Delete",
|
||||||
|
@ -162,6 +175,7 @@
|
||||||
"ViewHeader.search": "Search",
|
"ViewHeader.search": "Search",
|
||||||
"ViewHeader.search-text": "Search text",
|
"ViewHeader.search-text": "Search text",
|
||||||
"ViewHeader.select-a-template": "Select a template",
|
"ViewHeader.select-a-template": "Select a template",
|
||||||
|
"ViewHeader.set-default-template": "Set as default",
|
||||||
"ViewHeader.share-board": "Share board",
|
"ViewHeader.share-board": "Share board",
|
||||||
"ViewHeader.sort": "Sort",
|
"ViewHeader.sort": "Sort",
|
||||||
"ViewHeader.untitled": "Untitled",
|
"ViewHeader.untitled": "Untitled",
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"check": "eslint --ext .tsx,.ts . --quiet --cache && stylelint **/*.scss",
|
"check": "eslint --ext .tsx,.ts . --quiet --cache && stylelint **/*.scss",
|
||||||
"fix": "eslint --ext .tsx,.ts . --quiet --fix --cache && stylelint --fix **/*.scss",
|
"fix": "eslint --ext .tsx,.ts . --quiet --fix --cache && stylelint --fix **/*.scss",
|
||||||
"fix:scss": "prettier --write './src/**/*.scss'",
|
"fix:scss": "prettier --write './src/**/*.scss'",
|
||||||
"i18n-extract": "formatjs extract src/*/*/*.ts? src/*/*.ts? src/*.ts? --out-file i18n/tmp.json; formatjs compile i18n/tmp.json --out-file i18n/en.json; rm i18n/tmp.json",
|
"i18n-extract": "formatjs extract ../mattermost-plugin/webapp/src/*/*/*.ts? src/*/*/*.ts? src/*/*.ts? src/*.ts? --out-file i18n/tmp.json; formatjs compile i18n/tmp.json --out-file i18n/en.json; rm i18n/tmp.json",
|
||||||
"runserver-test": "cd cypress && cross-env FOCALBOARD_SINGLE_USER_TOKEN=TESTTOKEN ../../bin/focalboard-server -single-user",
|
"runserver-test": "cd cypress && cross-env FOCALBOARD_SINGLE_USER_TOKEN=TESTTOKEN ../../bin/focalboard-server -single-user",
|
||||||
"cypress:ci": "start-server-and-test runserver-test http://localhost:8088 cypress:run",
|
"cypress:ci": "start-server-and-test runserver-test http://localhost:8088 cypress:run",
|
||||||
"cypress:run": "cypress run",
|
"cypress:run": "cypress run",
|
||||||
|
|
|
@ -214,6 +214,15 @@ class OctoClient {
|
||||||
return this.getBlocksWithPath(path)
|
return this.getBlocksWithPath(path)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getBlocksWithBlockID(blockID: string, workspaceID?: string, optionalReadToken?: string): Promise<Block[]> {
|
||||||
|
let path = this.workspacePath(workspaceID) + `/blocks?block_id=${blockID}`
|
||||||
|
const readToken = optionalReadToken || Utils.getReadToken()
|
||||||
|
if (readToken) {
|
||||||
|
path += `&read_token=${readToken}`
|
||||||
|
}
|
||||||
|
return this.getBlocksWithPath(path)
|
||||||
|
}
|
||||||
|
|
||||||
async getAllBlocks(): Promise<Block[]> {
|
async getAllBlocks(): Promise<Block[]> {
|
||||||
const path = this.workspacePath() + '/blocks?all=true'
|
const path = this.workspacePath() + '/blocks?all=true'
|
||||||
return this.getBlocksWithPath(path)
|
return this.getBlocksWithPath(path)
|
||||||
|
|
Loading…
Reference in a new issue