mirror of
https://github.com/mattermost/focalboard.git
synced 2025-03-03 15:32:14 +02:00
Merge remote-tracking branch 'origin/main' into main
This commit is contained in:
commit
716551bc7c
4
.github/workflows/dev-release.yml
vendored
4
.github/workflows/dev-release.yml
vendored
@ -68,7 +68,7 @@ jobs:
|
||||
path: ${{ github.workspace }}/linux/dist/focalboard-linux.tar.gz
|
||||
|
||||
macos:
|
||||
runs-on: macos-10.15
|
||||
runs-on: macos-11
|
||||
|
||||
steps:
|
||||
|
||||
@ -101,7 +101,7 @@ jobs:
|
||||
- name: Build macOS
|
||||
run: make mac-app
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_13.2.1.app/Contents/Developer
|
||||
BUILD_NUMBER: ${{ github.run_id }}
|
||||
|
||||
- name: Upload macOS package
|
||||
|
4
.github/workflows/prod-release.yml
vendored
4
.github/workflows/prod-release.yml
vendored
@ -63,7 +63,7 @@ jobs:
|
||||
path: ${{ github.workspace }}/linux/dist/focalboard-linux.tar.gz
|
||||
|
||||
macos:
|
||||
runs-on: macos-10.15
|
||||
runs-on: macos-11
|
||||
|
||||
steps:
|
||||
|
||||
@ -96,7 +96,7 @@ jobs:
|
||||
- name: Build macOS
|
||||
run: make mac-app
|
||||
env:
|
||||
DEVELOPER_DIR: /Applications/Xcode_12.4.app/Contents/Developer
|
||||
DEVELOPER_DIR: /Applications/Xcode_13.2.1.app/Contents/Developer
|
||||
BUILD_NUMBER: ${{ github.run_id }}
|
||||
|
||||
- name: Upload macOS package
|
||||
|
@ -1,33 +1,49 @@
|
||||
# Code Contribution Guidelines
|
||||
|
||||
Thank you for your interest in contributing! Please see the [Focalboard Contribution Guide](https://www.focalboard.com/contribute/getting-started/) which describes the process for making code contributions, and [join our Focalboard community channel](https://community.mattermost.com/core/channels/focalboard) to ask questions from community members and the core team.
|
||||
Thank you for your interest in contributing! Please see the [Focalboard Contribution Guide](https://mattermost.github.io/focalboard/) which describes the process for making code contributions, and [join our Focalboard community channel](https://community.mattermost.com/core/channels/focalboard) to ask questions from community members and the core team.
|
||||
|
||||
When you submit a pull request, it goes through a [code review process outlined here](https://www.focalboard.com/contribute/getting-started/code-review/).
|
||||
When you submit a pull request, it goes through a [code review process outlined here](https://mattermost.github.io/focalboard/code-review).
|
||||
|
||||
# Updating Changelog
|
||||
|
||||
After a noteable bug fix or an improvement you've submitted is merged, please consider making a pull request to the [CHANGELOG.md](https://github.com/mattermost/focalboard/blob/main/CHANGELOG.md) under the next release section.
|
||||
After a noteable bug fix or an improvement you've submitted is merged, please consider making a pull request to the [CHANGELOG.md](https://github.com/mattermost/focalboard/blob/main/CHANGELOG.md) under the next release section.
|
||||
|
||||
# Bug reports
|
||||
|
||||
Please file a [GitHub issue](https://github.com/mattermost/focalboard/issues) if anything isn't working the way you expect.
|
||||
|
||||
# Documentation
|
||||
# Documentation
|
||||
|
||||
You can contribute to our documentation in the [Mattermost Boards documentation](https://docs.mattermost.com/guides/boards.html). Read more about how the contribution process works in the repo [README](https://github.com/mattermost/docs/blob/master/README.md). Visit the [Documentation Working Group channel](https://community.mattermost.com/core/channels/dwg-documentation-working-group) on our Community server if you have any questions!
|
||||
|
||||
# Contributors
|
||||
|
||||
**Quality Assurance Contributors**: Correctly files verified bug reports.
|
||||
**Core Committers**: A core committer is a maintainer on the Focalboard project who has merge access to the repositories. They are responsible for reviewing pull requests, cultivating the developer community, and guiding the technical vision of Focalboard. If you have a question or need some help, these are the people to ask.
|
||||
|
||||
- [chenilim](https://github.com/chenilim)
|
||||
- **<a name="scott.bishel">Scott Bishel</a>**
|
||||
- @scott.bishel on [community.mattermost.com](https://community.mattermost.com/core/messages/@scott.bishel) and [@sbishel](https://github.com/sbishel) on GitHub
|
||||
- **<a name="jesús.espino">Jesús Espino</a>**
|
||||
- @jesus.espino on [community.mattermost.com](https://community.mattermost.com/core/messages/@jesus.espino) and [@jespino](https://github.com/jespino) on GitHub
|
||||
- **<a name="doug.lauder">Doug Lauder</a>**
|
||||
- @doug.lauder on [community.mattermost.com](https://community.mattermost.com/core/messages/@doug.lauder) and [@wiggin77](https://github.com/wiggin77) on GitHub
|
||||
- **<a name="miguel.delacruz">Miguel de la Cruz</a>**
|
||||
- @miguel.delacruz on [community.mattermost.com](https://community.mattermost.com/core/messages/@miguel.delacruz) and [@mgdelacroix](https://github.com/mgdelacroix) on GitHub
|
||||
- **<a name="harshil.sharma">Harshil Sharma</a>**
|
||||
- @harshil.sharma on [community.mattermost.com](https://community.mattermost.com/core/messages/@harshil.sharma) and [@harshilsharma63](https://github.com/harshilsharma63) on GitHub
|
||||
- **<a name="chen.lim">Chen Lim</a>**
|
||||
- @chen-i.lim on [community.mattermost.com](https://community.mattermost.com/core/messages/@chen-i.lim) and [@chenilim](https://github.com/chenilim) on GitHub
|
||||
|
||||
**Quality Assurance**: Checks quality of code and verifies bug fixes.
|
||||
|
||||
- **<a name="ogi.marusic">Ogi Marušić</a>**
|
||||
- @ogi.marusic on [community.mattermost.com](https://community.mattermost.com/core/messages/@ogi.marusic) and [@ogi-m](https://github.com/ogi-m) on GitHub
|
||||
|
||||
**Community Organizers**: Responds with comments to Bug Reports, Issues, and Pull Requests with tags, edits and mentions to Core Committers and contributors.
|
||||
|
||||
- [it33](https://github.com/it33)
|
||||
- **<a name="winson.wu">Winson Wu</a>**
|
||||
- @winson.wu on [community.mattermost.com](https://community.mattermost.com/core/messages/@winson.wu) and [@wuwinson](https://github.com/wuwinson) on GitHub
|
||||
|
||||
**Core Committers**: Updates project. Approves and merges pull requests from contributors.
|
||||
**Documentation**: Verifies documentation changes, and updates documentation for new features.
|
||||
|
||||
- [chenilim](https://github.com/chenilim)
|
||||
- [jespino](https://github.com/jespino)
|
||||
- [coreyhulen](https://github.com/coreyhulen)
|
||||
- **<a name="justine.geffen">Justine Geffen</a>**
|
||||
- @justine.geffen on [community.mattermost.com](https://community.mattermost.com/core/messages/@justine.geffen) and [@justinegeffen ](https://github.com/justinegeffen) on GitHub
|
||||
|
@ -45,7 +45,7 @@ Download the latest server release from [GitHub releases](https://github.com/mat
|
||||
|
||||
## Building the server
|
||||
|
||||
Most development can be done on the Personal Server edition. Please refer to the [Developer's Tips & Tricks](https://www.focalboard.com/contribute/getting-started/dev-tips/) for more detailed steps. Here's a summary:
|
||||
Most development can be done on the Personal Server edition. Please refer to the [Developer's Tips & Tricks](https://mattermost.github.io/focalboard/dev-tips) for more detailed steps. Here's a summary:
|
||||
|
||||
First, install basic dependencies:
|
||||
* Go 1.15+
|
||||
@ -72,7 +72,7 @@ You can build standalone apps that package the server to run locally against SQL
|
||||
* Mac:
|
||||
* `make mac-app`
|
||||
* run `mac/dist/Focalboard.app`
|
||||
* *Requires: macOS Catalina (10.15)+, Xcode 12+.*
|
||||
* *Requires: macOS 11.3+, Xcode 13.2.1+*
|
||||
* Linux:
|
||||
* Install webgtk dependencies
|
||||
* `sudo apt-get install libgtk-3-dev`
|
||||
|
3
docs/_config.yml
Normal file
3
docs/_config.yml
Normal file
@ -0,0 +1,3 @@
|
||||
title: Focalboard Developers
|
||||
google_analytics: UA-64458817-2
|
||||
theme: jekyll-theme-architect
|
@ -1,19 +1,10 @@
|
||||
---
|
||||
title: "Code Review"
|
||||
date: 2021-02-03T08:00:00-00:00
|
||||
weight: 5
|
||||
subsection: Getting Started
|
||||
---
|
||||
# Code Review Checklist
|
||||
|
||||
Currently, all changes to the product must be reviewed by a [core committer](/contribute/getting-started/core-committers/#core-committers).
|
||||
Currently, all changes to the product must be reviewed by a [core committer](core-committers.md).
|
||||
|
||||
<!-- * Documentation changes must be reviewed by a [product manager](/contribute/getting-started/core-committers/#product-managers).
|
||||
* Product managers may ask for reviews from [core committers](/contribute/getting-started/core-committers/#core-committers) and [QA testers](/contribute/getting-started/core-committers/#qa-testers) as required. -->
|
||||
## If you are a community member seeking a review
|
||||
|
||||
If you are a community member seeking a review
|
||||
----------------------------------------------
|
||||
|
||||
1. Submit your pull request.
|
||||
1. Submit your pull request (PR).
|
||||
* Follow the [contribution checklist](../contribution-checklist/).
|
||||
2. Wait for a reviewer to be assigned.
|
||||
* Product managers are on the lookout for new pull requests and usually handle this for you automatically.
|
||||
@ -23,16 +14,15 @@ If you are a community member seeking a review
|
||||
3. [Wait for a review](#if-you-are-awaiting-a-review).
|
||||
* Expect some interaction with at least one reviewer within 5 business days (weekdays, Monday through Friday, excluding [statutory holidays](https://docs.mattermost.com/process/working-at-mattermost.html#holidays)).
|
||||
* Keep in mind that core committers are geographically distributed around the world and likely in a different time zone than your own.
|
||||
* If no interaction has occurred after 5 business days, please at-mention a reviewer with a comment on your PR.
|
||||
* If no interaction has occurred after 5 business days, please [at-mention](https://github.blog/2011-03-23-mention-somebody-they-re-notified/) a reviewer with a comment on your pull request.
|
||||
4. Make any necessary changes.
|
||||
* If a reviewer requests changes, your pull request will disappear from their queue of reviews.
|
||||
* Once you've addressed the concerns, please at-mention the reviewer with a comment on your PR.
|
||||
* Once you've addressed the concerns, please at-mention the reviewer with a comment on your pull request.
|
||||
5. Wait for your code to be merged.
|
||||
* Larger pull requests may require more time to review.
|
||||
* Once all reviewers have approved your changes, they will handle merging your code.
|
||||
|
||||
If you are awaiting a review
|
||||
----------------------------
|
||||
## If you are awaiting a review
|
||||
|
||||
1. Wait patiently for reviews to complete.
|
||||
* Expect some interaction with each of your reviewers within 5 business days.
|
||||
@ -41,8 +31,7 @@ If you are awaiting a review
|
||||
* If a reviewer requests changes, your pull request will disappear from their queue of reviews.
|
||||
* Once you've addressed the concerns, assign them as a reviewer again to put your pull request back in their queue.
|
||||
|
||||
If you are a core committer asked to give a review
|
||||
--------------------------------------------------
|
||||
## If you are a core committer asked to give a review
|
||||
|
||||
1. Respond promptly to requested reviews.
|
||||
* Assume the requested review is urgent and blocking unless explicitly stated otherwise.
|
@ -1,27 +1,16 @@
|
||||
---
|
||||
title: "Contribution Checklist"
|
||||
date: 2021-02-03T08:00:00-00:00
|
||||
weight: 2
|
||||
subsection: Getting Started
|
||||
---
|
||||
# Contribution Checklist
|
||||
|
||||
Thanks for your interest in contributing code!
|
||||
|
||||
<!-- Come join our [Contributors community channel](https://community.mattermost.com/core/channels/tickets) on the community server, where you can discuss questions with community members and the Focalboard core team. -->
|
||||
|
||||
<!-- To help with translations, [see the localization process](https://docs.mattermost.com/developer/localization.html). -->
|
||||
|
||||
Follow this checklist for submitting a pull request (PR):
|
||||
|
||||
1. You've signed the [Contributor License Agreement](http://www.mattermost.org/mattermost-contributor-agreement/), so you can be added to the Mattermost [Approved Contributor List](https://docs.google.com/spreadsheets/d/1NTCeG-iL_VS9bFqtmHSfwETo5f-8MQ7oMDE5IUYJi_Y/pubhtml?gid=0&single=true).
|
||||
2. Your ticket is a Help Wanted GitHub issue for the project you're contributing to.
|
||||
- If not, follow the process [here](/contribute/getting-started/contributions-without-ticket).
|
||||
- If not, follow the process [here](contributions-without-ticket.md).
|
||||
3. Your code is thoroughly tested, including appropriate unit tests, and manual testing.
|
||||
4. If applicable, user interface strings are included in the localization file ([en.json](https://github.com/mattermost/focalboard/blob/main/webapp/i18n/en.json))
|
||||
- In the webapp folder, run `npm run i18n-extract` to generate the new/updated strings.
|
||||
5. The PR is submitted against the `main` branch from your fork.
|
||||
6. The PR title begins with the GitHub Ticket ID (e.g. `[GH-394]`) and the summary template is filled out.
|
||||
|
||||
Once submitted, the automated build process must pass in order for the PR to be accepted. Any errors or failures need to be addressed in order for the PR to be accepted. Next, the PR goes through [code review](../code-review/). To learn about the review process for each project, read the `CONTRIBUTING.md` file of that GitHub repository.
|
||||
|
||||
<!-- That's all! If you have any feedback about this checklist, let us know in the [Contributors channel](https://community.mattermost.com/core/channels/tickets). -->
|
||||
Once submitted, the automated build process must pass in order for the PR to be accepted. Any errors or failures need to be addressed in order for the PR to be accepted. Next, the PR goes through [code review](code-review.md). To learn about the review process for each project, read the [CONTRIBUTING.md](https://github.com/mattermost/focalboard/blob/main/CONTRIBUTING.md) file of that GitHub repository.
|
@ -1,9 +1,4 @@
|
||||
---
|
||||
title: "Contributions Without Ticket"
|
||||
date: 2021-02-03T08:00:00-00:00
|
||||
weight: 3
|
||||
subsection: Getting Started
|
||||
---
|
||||
# Contributions Without Ticket
|
||||
|
||||
Contributions for minor corrections and improvements without a corresponding `Help Wanted` ticket are welcome. For example, a pull request for a bug or incremental improvement, with less than 20 lines of code change, is usually accepted if the change to existing behaviour is minor.
|
||||
|
20
docs/core-committers.md
Normal file
20
docs/core-committers.md
Normal file
@ -0,0 +1,20 @@
|
||||
# Core Committers
|
||||
|
||||
A core committer is a maintainer on the Focalboard project who has merge access to the repositories. They are responsible for reviewing pull requests, cultivating the developer community, and guiding the technical vision of Focalboard. If you have a question or need some help, these are the people to ask.
|
||||
|
||||
## Core Committers
|
||||
|
||||
Below is the list of core committers working on Focalboard:
|
||||
|
||||
- **<a name="scott.bishel">Scott Bishel</a>**
|
||||
- @scott.bishel on [community.mattermost.com](https://community.mattermost.com/core/messages/@scott.bishel) and [@sbishel](https://github.com/sbishel) on GitHub
|
||||
- **<a name="jesús.espino">Jesús Espino</a>**
|
||||
- @jesus.espino on [community.mattermost.com](https://community.mattermost.com/core/messages/@jesus.espino) and [@jespino](https://github.com/jespino) on GitHub
|
||||
- **<a name="doug.lauder">Doug Lauder</a>**
|
||||
- @doug.lauder on [community.mattermost.com](https://community.mattermost.com/core/messages/@doug.lauder) and [@wiggin77](https://github.com/wiggin77) on GitHub
|
||||
- **<a name="miguel.delacruz">Miguel de la Cruz</a>**
|
||||
- @miguel.delacruz on [community.mattermost.com](https://community.mattermost.com/core/messages/@miguel.delacruz) and [@mgdelacroix](https://github.com/mgdelacroix) on GitHub
|
||||
- **<a name="harshil.sharma">Harshil Sharma</a>**
|
||||
- @harshil.sharma on [community.mattermost.com](https://community.mattermost.com/core/messages/@harshil.sharma) and [@harshilsharma63](https://github.com/harshilsharma63) on GitHub
|
||||
- **<a name="chen.lim">Chen Lim</a>**
|
||||
- @chen-i.lim on [community.mattermost.com](https://community.mattermost.com/core/messages/@chen-i.lim) and [@chenilim](https://github.com/chenilim) on GitHub
|
@ -1,11 +1,6 @@
|
||||
---
|
||||
title: "Developer Tips & Tricks"
|
||||
date: 2021-02-03T00:08:00-00:00
|
||||
weight: 1
|
||||
subsection: Getting Started
|
||||
---
|
||||
# Developer Tips and Tricks
|
||||
|
||||
## Install pre-reqs
|
||||
## Installation prerequisites
|
||||
|
||||
Check that you have recent versions of the basic dependencies installed:
|
||||
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
|
||||
@ -16,13 +11,14 @@ Check that you have recent versions of the basic dependencies installed:
|
||||
On Windows:
|
||||
* Install [Mingw64](https://chocolatey.org/packages/mingw) via [Chocolatey](https://chocolatey.org/)
|
||||
|
||||
On Mac, to build the Mac app:
|
||||
On macOS, to build the Mac app:
|
||||
* Install [Xcode](https://apps.apple.com/us/app/xcode/id497799835?mt=12) (v12+)
|
||||
* Install the Xcode commandline tools, via the IDE or run `xcode-select --install`
|
||||
|
||||
On Linux, to build the Linux app:
|
||||
* `sudo apt-get install libgtk-3-dev`
|
||||
* `sudo apt-get install libwebkit2gtk-4.0-dev`
|
||||
* `sudo apt-get install autoconf dh-autoreconf`
|
||||
|
||||
## Clone the project source code
|
||||
|
||||
@ -42,7 +38,7 @@ Then open a browser to `http://localhost:8000` to access it. The port is configu
|
||||
|
||||
Once the server is running, you can rebuild just the webapp with `make webapp` (in a separate terminal window), then reload the browser.
|
||||
|
||||
## VSCode Setup
|
||||
## VSCode setup
|
||||
|
||||
Here's a recommended dev-test loop using VSCode:
|
||||
* Open a bash terminal window to the project folder
|
||||
@ -73,24 +69,22 @@ Debug the Go code in VSCode. This is set up automatically when you launch the se
|
||||
|
||||
To start, add a breakpoint to `handleGetBlocks()` in `server/api/api.go`, then refresh the browser to see how data is retrieved.
|
||||
|
||||
## Localization / Internationalization / Translation
|
||||
## Localization/Internationalization/Translation
|
||||
|
||||
We use `i18n` to localize the web app. Localized string generally use `intl.formatMessage`.
|
||||
|
||||
When adding or modifying localized strings, run `npm run i18n-extract` in `webapp` to rebuild `webapp/i18n/en.json`.
|
||||
We use `i18n` to localize the web app. Localized string generally use `intl.formatMessage`. When adding or modifying localized strings, run `npm run i18n-extract` in `webapp` to rebuild `webapp/i18n/en.json`.
|
||||
|
||||
Translated strings are stored in other json files under `webapp/i18n`, e.g. `es.json` for Spanish.
|
||||
|
||||
## Database
|
||||
|
||||
By default, data is stored in a sqlite datebase `focalboard.db`. You can view and edit this directly using `sqlite3 focalboard.db` from bash.
|
||||
By default, data is stored in a sqlite database `focalboard.db`. You can view and edit this directly using `sqlite3 focalboard.db` from bash.
|
||||
|
||||
## Unit tests
|
||||
|
||||
Before checking-in commits, run: `make ci`, which is simlar to the ci.yml workflow and includes:
|
||||
* Server unit tests: `make server-test`
|
||||
* Webapp eslint: `cd webapp; npm run check`
|
||||
* Webapp unit tests: `cd webapp; npm run test`
|
||||
* Webapp unit tests: `make webapp-test`
|
||||
* Webapp UI tests: `cd webapp; npm run cypress:ci`
|
||||
|
||||
## Running into problems or have questions?
|
19
docs/index.md
Normal file
19
docs/index.md
Normal file
@ -0,0 +1,19 @@
|
||||
# Focalboard / Mattermost Boards Contributors Guide
|
||||
|
||||
Welcome to the [Focalboard](https://www.focalboard.com) / [Mattermost Boards](https://mattermost.com/boards/?utm_source=focalboard) project!
|
||||
|
||||
We're very glad you want to check it out and perhaps contribute code our repository in GitHub.
|
||||
|
||||
Our goal is to make your experience as great as possible. Follow these simple steps to contribute:
|
||||
|
||||
1. [Clone the project from GitHub](https://github.com/mattermost/focalboard) and follow the steps in the README to build. Read the [developer tips & tricks](dev-tips.md) to get started.
|
||||
|
||||
2. Find [help wanted tickets in GitHub](https://github.com/mattermost/focalboard/issues?q=is%3Aopen+is%3Aissue+label%3A%22help+wanted%22). Comment to let everyone know you’re working on it, and to allow a core contributor to assign the issue to you. If there’s no ticket for what you want to work on see [contributions without a ticket.](contributions-without-ticket.md).
|
||||
|
||||
3. When your changes are ready, run through our [checklist for pull requests](contribution-checklist.md). Note that if it’s your first contribution, there is a standard [CLA](https://www.mattermost.org/mattermost-contributor-agreement/) to sign.
|
||||
|
||||
## Just ask if you need help!
|
||||
|
||||
You can find us on our [public Focalboard channel](https://community.mattermost.com/core/channels/focalboard) on the Mattermost community server. Also feel free to [file a bug](https://github.com/mattermost/focalboard/issues/new/choose) for any issues you run into, or [start a discussion](https://github.com/mattermost/focalboard/discussions).
|
||||
|
||||
We're glad ❤️ you're here! Good luck and have fun!
|
@ -8,4 +8,4 @@ This subfolder contains scripts to import data from other systems. It is at an e
|
||||
* Todoist
|
||||
* Nextcloud Deck
|
||||
|
||||
[Contribute code](https://www.focalboard.com/contribute/getting-started/) to expand this.
|
||||
[Contribute code](https://mattermost.github.io/focalboard/) to expand this.
|
||||
|
@ -10,4 +10,4 @@ This node app converts an Asana json archive into a Focalboard archive. To use:
|
||||
|
||||
## Import scope
|
||||
|
||||
Currently, the script imports all cards from a single board, including their section (column) membership, names, and notes. [Contribute code](https://www.focalboard.com/contribute/getting-started/) to expand this.
|
||||
Currently, the script imports all cards from a single board, including their section (column) membership, names, and notes. [Contribute code](https://mattermost.github.io/focalboard/) to expand this.
|
||||
|
@ -3,7 +3,7 @@
|
||||
import * as fs from 'fs'
|
||||
import minimist from 'minimist'
|
||||
import {exit} from 'process'
|
||||
import {ArchiveUtils} from '../../webapp/src/blocks/archive'
|
||||
import {ArchiveUtils} from '../util/archive'
|
||||
import {Block} from '../../webapp/src/blocks/block'
|
||||
import {IPropertyOption, IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'
|
||||
import {createBoardView} from '../../webapp/src/blocks/boardView'
|
||||
|
@ -20,4 +20,4 @@ The following aren't currently imported:
|
||||
* Comments
|
||||
* Embedded files
|
||||
|
||||
[Contribute code](https://www.focalboard.com/contribute/getting-started/) to expand this.
|
||||
[Contribute code](https://mattermost.github.io/focalboard/) to expand this.
|
||||
|
@ -2,7 +2,7 @@
|
||||
// See LICENSE.txt for license information.
|
||||
import * as fs from 'fs'
|
||||
import {exit} from 'process'
|
||||
import {ArchiveUtils} from '../../webapp/src/blocks/archive'
|
||||
import {ArchiveUtils} from '../util/archive'
|
||||
import {Block} from '../../webapp/src/blocks/block'
|
||||
import {IPropertyOption, IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'
|
||||
import {createBoardView} from '../../webapp/src/blocks/boardView'
|
||||
|
@ -11,6 +11,6 @@ This node app converts data from a Nextcloud Server with the [app Deck](https://
|
||||
|
||||
## Import scope
|
||||
|
||||
Currently, the script imports all cards from a single board, including their stacks (column) membership, labels, names, descriptions, duedate and comments. [Contribute code](https://www.focalboard.com/contribute/getting-started/) to expand this.
|
||||
Currently, the script imports all cards from a single board, including their stacks (column) membership, labels, names, descriptions, duedate and comments. [Contribute code](https://mattermost.github.io/focalboard/) to expand this.
|
||||
|
||||
|
||||
|
@ -15,4 +15,4 @@ Currently, the script imports all cards from a single board, including their pro
|
||||
|
||||
The Notion export format does not preserve property types, so the script currently imports all card properties as a Select type. You can change the type after importing into Focalboard.
|
||||
|
||||
[Contribute code](https://www.focalboard.com/contribute/getting-started/) to expand this.
|
||||
[Contribute code](https://mattermost.github.io/focalboard/) to expand this.
|
||||
|
@ -3,7 +3,7 @@ import * as fs from 'fs'
|
||||
import minimist from 'minimist'
|
||||
import path from 'path'
|
||||
import {exit} from 'process'
|
||||
import {ArchiveUtils} from '../../webapp/src/blocks/archive'
|
||||
import {ArchiveUtils} from '../util/archive'
|
||||
import {Block} from '../../webapp/src/blocks/block'
|
||||
import {IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'
|
||||
import {createBoardView} from '../../webapp/src/blocks/boardView'
|
||||
|
@ -3,7 +3,7 @@
|
||||
import * as fs from 'fs'
|
||||
import minimist from 'minimist'
|
||||
import {exit} from 'process'
|
||||
import {ArchiveUtils} from '../../webapp/src/blocks/archive'
|
||||
import {ArchiveUtils} from '../util/archive'
|
||||
import {Block} from '../../webapp/src/blocks/block'
|
||||
import {IPropertyOption, IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'
|
||||
import {createBoardView} from '../../webapp/src/blocks/boardView'
|
||||
|
@ -11,6 +11,6 @@ This node app converts a Trello json archive into a Focalboard archive. To use:
|
||||
|
||||
## Import scope
|
||||
|
||||
Currently, the script imports all cards from a single board, including their list (column) membership, names, and descriptions. [Contribute code](https://www.focalboard.com/contribute/getting-started/) to expand this.
|
||||
Currently, the script imports all cards from a single board, including their list (column) membership, names, and descriptions. [Contribute code](https://mattermost.github.io/focalboard/) to expand this.
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
import * as fs from 'fs'
|
||||
import minimist from 'minimist'
|
||||
import {exit} from 'process'
|
||||
import {ArchiveUtils} from '../../webapp/src/blocks/archive'
|
||||
import {ArchiveUtils} from '../util/archive'
|
||||
import {Block} from '../../webapp/src/blocks/block'
|
||||
import {IPropertyOption, IPropertyTemplate, createBoard} from '../../webapp/src/blocks/board'
|
||||
import {createBoardView} from '../../webapp/src/blocks/boardView'
|
||||
|
81
import/util/archive.ts
Normal file
81
import/util/archive.ts
Normal file
@ -0,0 +1,81 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {Block} from '../../webapp/src/blocks/block'
|
||||
|
||||
interface ArchiveHeader {
|
||||
version: number
|
||||
date: number
|
||||
}
|
||||
|
||||
interface ArchiveLine {
|
||||
type: string,
|
||||
data: unknown,
|
||||
}
|
||||
|
||||
// This schema allows the expansion of additional line types in the future
|
||||
interface BlockArchiveLine extends ArchiveLine {
|
||||
type: 'block',
|
||||
data: Block
|
||||
}
|
||||
|
||||
class ArchiveUtils {
|
||||
static buildBlockArchive(blocks: readonly Block[]): string {
|
||||
const header: ArchiveHeader = {
|
||||
version: 1,
|
||||
date: Date.now(),
|
||||
}
|
||||
|
||||
const headerString = JSON.stringify(header)
|
||||
let content = headerString + '\n'
|
||||
for (const block of blocks) {
|
||||
const line: BlockArchiveLine = {
|
||||
type: 'block',
|
||||
data: block,
|
||||
}
|
||||
const lineString = JSON.stringify(line)
|
||||
content += lineString
|
||||
content += '\n'
|
||||
}
|
||||
|
||||
return content
|
||||
}
|
||||
|
||||
static parseBlockArchive(contents: string): Block[] {
|
||||
const blocks: Block[] = []
|
||||
const allLineStrings = contents.split('\n')
|
||||
if (allLineStrings.length >= 2) {
|
||||
const headerString = allLineStrings[0]
|
||||
const header = JSON.parse(headerString) as ArchiveHeader
|
||||
if (header.date && header.version >= 1) {
|
||||
const lineStrings = allLineStrings.slice(1)
|
||||
let lineNum = 2
|
||||
for (const lineString of lineStrings) {
|
||||
if (!lineString) {
|
||||
// Ignore empty lines, e.g. last line
|
||||
continue
|
||||
}
|
||||
const line = JSON.parse(lineString) as ArchiveLine
|
||||
if (!line || !line.type || !line.data) {
|
||||
throw new Error(`ERROR parsing line ${lineNum}`)
|
||||
}
|
||||
switch (line.type) {
|
||||
case 'block': {
|
||||
const blockLine = line as BlockArchiveLine
|
||||
const block = blockLine.data
|
||||
blocks.push(block)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
lineNum += 1
|
||||
}
|
||||
} else {
|
||||
throw new Error('ERROR parsing header')
|
||||
}
|
||||
}
|
||||
|
||||
return blocks
|
||||
}
|
||||
}
|
||||
|
||||
export {ArchiveHeader, ArchiveLine, BlockArchiveLine, ArchiveUtils}
|
@ -9,6 +9,7 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
8014951C261598D600A51700 /* PortUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8014951B261598D600A51700 /* PortUtils.swift */; };
|
||||
804E57FC27441B6B008526F0 /* whatsnew.txt in Resources */ = {isa = PBXBuildFile; fileRef = 804E57FB27441B6B008526F0 /* whatsnew.txt */; };
|
||||
80672A8B27BAFEBA00257B8C /* DownloadHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80672A8A27BAFEBA00257B8C /* DownloadHandler.swift */; };
|
||||
80D6DEBB252E13CB00AEED9E /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D6DEBA252E13CB00AEED9E /* AppDelegate.swift */; };
|
||||
80D6DEBD252E13CB00AEED9E /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 80D6DEBC252E13CB00AEED9E /* ViewController.swift */; };
|
||||
80D6DEBF252E13CD00AEED9E /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 80D6DEBE252E13CD00AEED9E /* Assets.xcassets */; };
|
||||
@ -42,6 +43,7 @@
|
||||
/* Begin PBXFileReference section */
|
||||
8014951B261598D600A51700 /* PortUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PortUtils.swift; sourceTree = "<group>"; };
|
||||
804E57FB27441B6B008526F0 /* whatsnew.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = whatsnew.txt; sourceTree = "<group>"; };
|
||||
80672A8A27BAFEBA00257B8C /* DownloadHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadHandler.swift; sourceTree = "<group>"; };
|
||||
80D6DEB7252E13CB00AEED9E /* Focalboard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Focalboard.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
80D6DEBA252E13CB00AEED9E /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||
80D6DEBC252E13CB00AEED9E /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
|
||||
@ -116,6 +118,7 @@
|
||||
80F8BF572624EB0C00FF3943 /* Globals.swift */,
|
||||
8014951B261598D600A51700 /* PortUtils.swift */,
|
||||
80D6DEBC252E13CB00AEED9E /* ViewController.swift */,
|
||||
80672A8A27BAFEBA00257B8C /* DownloadHandler.swift */,
|
||||
80F174B62788C1A2000A9EEA /* CustomWKWebView.swift */,
|
||||
80F8BF4F2624E1BB00FF3943 /* WhatsNewViewController.swift */,
|
||||
804E57FB27441B6B008526F0 /* whatsnew.txt */,
|
||||
@ -303,6 +306,7 @@
|
||||
80F8BF502624E1BB00FF3943 /* WhatsNewViewController.swift in Sources */,
|
||||
80F174B72788C1A2000A9EEA /* CustomWKWebView.swift in Sources */,
|
||||
80F8BF582624EB0C00FF3943 /* Globals.swift in Sources */,
|
||||
80672A8B27BAFEBA00257B8C /* DownloadHandler.swift in Sources */,
|
||||
80D6DF18252F9BDE00AEED9E /* AutoSaveWindowController.swift in Sources */,
|
||||
80D6DEBD252E13CB00AEED9E /* ViewController.swift in Sources */,
|
||||
80D6DEBB252E13CB00AEED9E /* AppDelegate.swift in Sources */,
|
||||
@ -403,7 +407,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.3;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
@ -458,7 +462,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.14;
|
||||
MACOSX_DEPLOYMENT_TARGET = 11.3;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
MTL_FAST_MATH = YES;
|
||||
SDKROOT = macosx;
|
||||
|
40
mac/Focalboard/DownloadHandler.swift
Normal file
40
mac/Focalboard/DownloadHandler.swift
Normal file
@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import Foundation
|
||||
import WebKit
|
||||
|
||||
class DownloadHandler: NSObject, WKDownloadDelegate {
|
||||
func download(_ download: WKDownload, decideDestinationUsing response: URLResponse, suggestedFilename: String, completionHandler: @escaping (URL?) -> Void) {
|
||||
DispatchQueue.main.async {
|
||||
// Let user select location of file
|
||||
let savePanel = NSSavePanel()
|
||||
savePanel.canCreateDirectories = true
|
||||
savePanel.nameFieldStringValue = suggestedFilename
|
||||
// BUGBUG: Specifying the allowedFileTypes causes Catalina to hang / error out
|
||||
//savePanel.allowedFileTypes = [".boardsarchive"]
|
||||
savePanel.begin { (result) in
|
||||
if result.rawValue == NSApplication.ModalResponse.OK.rawValue,
|
||||
let fileUrl = savePanel.url {
|
||||
if (FileManager.default.fileExists(atPath: fileUrl.path)) {
|
||||
// HACKHACK: WKWebView doesn't appear to overwrite files, so delete exsiting files first
|
||||
do {
|
||||
try FileManager.default.removeItem(at: fileUrl)
|
||||
} catch {
|
||||
let alert = NSAlert()
|
||||
alert.messageText = "Unable to replace \(fileUrl.path)"
|
||||
alert.addButton(withTitle: "OK")
|
||||
alert.alertStyle = .warning
|
||||
alert.runModal()
|
||||
}
|
||||
}
|
||||
completionHandler(fileUrl)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func downloadDidFinish(_ download: WKDownload) {
|
||||
NSLog("downloadDidFinish")
|
||||
}
|
||||
}
|
@ -12,6 +12,7 @@ class ViewController:
|
||||
@IBOutlet var webView: CustomWKWebView!
|
||||
private var didLoad = false
|
||||
private var refreshWebViewOnLoad = true
|
||||
private let downloadHandler = DownloadHandler()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
@ -100,60 +101,6 @@ class ViewController:
|
||||
webView.load(request)
|
||||
}
|
||||
|
||||
private func downloadJsonUrl(_ url: URL) {
|
||||
NSLog("downloadJsonUrl")
|
||||
let prefix = "data:text/json,"
|
||||
let urlString = url.absoluteString
|
||||
let encodedJson = String(urlString[urlString.index(urlString.startIndex, offsetBy: prefix.lengthOfBytes(using: .utf8))...])
|
||||
guard let json = encodedJson.removingPercentEncoding else {
|
||||
return
|
||||
}
|
||||
|
||||
// Form file name
|
||||
let dateFormatter = DateFormatter()
|
||||
dateFormatter.dateFormat = "yyyy-M-d"
|
||||
let dateString = dateFormatter.string(from: Date())
|
||||
let filename = "archive-\(dateString).focalboard"
|
||||
|
||||
// Save file
|
||||
let savePanel = NSSavePanel()
|
||||
savePanel.canCreateDirectories = true
|
||||
savePanel.nameFieldStringValue = filename
|
||||
// BUGBUG: Specifying the allowedFileTypes causes Catalina to hang / error out
|
||||
//savePanel.allowedFileTypes = [".focalboard"]
|
||||
savePanel.begin { (result) in
|
||||
if result.rawValue == NSApplication.ModalResponse.OK.rawValue,
|
||||
let fileUrl = savePanel.url {
|
||||
try? json.write(to: fileUrl, atomically: true, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func downloadCsvUrl(_ url: URL) {
|
||||
NSLog("downloadCsvUrl")
|
||||
let prefix = "data:text/csv;charset=utf-8,"
|
||||
let urlString = url.absoluteString
|
||||
let encodedContents = String(urlString[urlString.index(urlString.startIndex, offsetBy: prefix.lengthOfBytes(using: .utf8))...])
|
||||
guard let contents = encodedContents.removingPercentEncoding else {
|
||||
return
|
||||
}
|
||||
|
||||
let filename = "data.csv"
|
||||
|
||||
// Save file
|
||||
let savePanel = NSSavePanel()
|
||||
savePanel.canCreateDirectories = true
|
||||
savePanel.nameFieldStringValue = filename
|
||||
// BUGBUG: Specifying the allowedFileTypes causes Catalina to hang / error out
|
||||
//savePanel.allowedFileTypes = [".focalboard"]
|
||||
savePanel.begin { (result) in
|
||||
if result.rawValue == NSApplication.ModalResponse.OK.rawValue,
|
||||
let fileUrl = savePanel.url {
|
||||
try? contents.write(to: fileUrl, atomically: true, encoding: .utf8)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, runOpenPanelWith parameters: WKOpenPanelParameters, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping ([URL]?) -> Void) {
|
||||
NSLog("webView runOpenPanel")
|
||||
let openPanel = NSOpenPanel()
|
||||
@ -169,22 +116,30 @@ class ViewController:
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
|
||||
if let url = navigationAction.request.url {
|
||||
// Intercept archive downloads, and present native UI
|
||||
if (url.absoluteString.hasPrefix("data:text/json,")) {
|
||||
decisionHandler(.cancel)
|
||||
downloadJsonUrl(url)
|
||||
return
|
||||
}
|
||||
if (url.absoluteString.hasPrefix("data:text/csv;charset=utf-8,")) {
|
||||
decisionHandler(.cancel)
|
||||
downloadCsvUrl(url)
|
||||
return
|
||||
}
|
||||
NSLog("decidePolicyFor navigationAction: \(url.absoluteString)")
|
||||
// Handle downloads
|
||||
|
||||
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, preferences: WKWebpagePreferences, decisionHandler: @escaping (WKNavigationActionPolicy, WKWebpagePreferences) -> Void) {
|
||||
if navigationAction.shouldPerformDownload {
|
||||
decisionHandler(.download, preferences)
|
||||
} else {
|
||||
decisionHandler(.allow, preferences)
|
||||
}
|
||||
decisionHandler(.allow)
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
|
||||
if navigationResponse.canShowMIMEType {
|
||||
decisionHandler(.allow)
|
||||
} else {
|
||||
decisionHandler(.download)
|
||||
}
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, navigationAction: WKNavigationAction, didBecome download: WKDownload) {
|
||||
download.delegate = downloadHandler
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, navigationResponse: WKNavigationResponse, didBecome download: WKDownload) {
|
||||
download.delegate = downloadHandler
|
||||
}
|
||||
|
||||
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
|
||||
|
@ -172,7 +172,14 @@ export default class Plugin {
|
||||
}
|
||||
|
||||
if (rudderKey !== '') {
|
||||
rudderAnalytics.load(rudderKey, rudderUrl)
|
||||
const rudderCfg = {} as {setCookieDomain: string}
|
||||
if (siteURL && siteURL !== '') {
|
||||
try {
|
||||
rudderCfg.setCookieDomain = new URL(siteURL).hostname
|
||||
// eslint-disable-next-line no-empty
|
||||
} catch (_) {}
|
||||
}
|
||||
rudderAnalytics.load(rudderKey, rudderUrl, rudderCfg)
|
||||
|
||||
rudderAnalytics.identify(config?.telemetryid, {}, TELEMETRY_OPTIONS)
|
||||
|
||||
|
@ -18,3 +18,27 @@ brew install openapi-generator
|
||||
# Server API documentation
|
||||
|
||||
See the generated [server API documentation here](https://htmlpreview.github.io/?https://github.com/mattermost/focalboard/blob/main/server/swagger/docs/html/index.html).
|
||||
|
||||
# Differences for Mattermost Boards
|
||||
|
||||
The auto-generated Swagger API documentation is for Focalboard Personal Server. If you are calling the API on Mattermost Boards, the additional changes are:
|
||||
|
||||
### API URLs endpoint
|
||||
|
||||
The API endpoint is at `https://SERVERNAME/plugins/focalboard/api/`, e.g. `https://community.mattermost.com/plugins/focalboard/api/`.
|
||||
|
||||
### Use the Mattermost auth token
|
||||
|
||||
Refer to the [Mattermost API documentation here](https://api.mattermost.com/#tag/authentication) on how to obtain the auth token.
|
||||
|
||||
Pass this token as a bearer token to the Boards APIs, e.g.
|
||||
|
||||
```
|
||||
curl -i -H "X-Requested-With: XMLHttpRequest" -H 'Authorization: Bearer abcdefghijklmnopqrstuvwxyz' https://community.mattermost.com/plugins/focalboard/api/v1/workspaces
|
||||
```
|
||||
|
||||
Note that the `X-Requested-With: XMLHttpRequest` header is required to pass the CSRF check.
|
||||
|
||||
# We want to hear from you!
|
||||
|
||||
If you are planning on using the Boards API, we would love to hear about what you'd like to do, and how we can improve the APIs in the future. [See here](https://github.com/mattermost/focalboard/discussions/2139) for more details on how to connect.
|
||||
|
@ -11,7 +11,7 @@
|
||||
"BoardPage.syncFailed": "Board may be deleted or access revoked.",
|
||||
"BoardTemplateSelector.add-template": "New template",
|
||||
"BoardTemplateSelector.create-empty-board": "Create empty board",
|
||||
"BoardTemplateSelector.delete-template": "Delete template",
|
||||
"BoardTemplateSelector.delete-template": "Delete",
|
||||
"BoardTemplateSelector.description": "Choose a template to help you get started. Easily customize the template to fit your needs, or create an empty board to start from scratch.",
|
||||
"BoardTemplateSelector.edit-template": "Edit",
|
||||
"BoardTemplateSelector.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.",
|
||||
@ -81,6 +81,7 @@
|
||||
"CardDialog.delete-confirmation-dialog-heading": "Confirm card delete!",
|
||||
"CardDialog.editing-template": "You're editing a template.",
|
||||
"CardDialog.nocard": "This card doesn't exist or is inaccessible.",
|
||||
"CenterPanel.Share": "Share",
|
||||
"ColorOption.selectColor": "Select {color} Color",
|
||||
"Comment.delete": "Delete",
|
||||
"CommentsList.send": "Send",
|
||||
@ -161,9 +162,13 @@
|
||||
"RegistrationLink.description": "Share this link for others to create accounts:",
|
||||
"RegistrationLink.regenerateToken": "Regenerate token",
|
||||
"RegistrationLink.tokenRegenerated": "Registration link regenerated",
|
||||
"ShareBoard.PublishDescription": "Publish and share a “read only” link with everyone on the web",
|
||||
"ShareBoard.PublishTitle": "Publish to the web",
|
||||
"ShareBoard.Title": "Share Board",
|
||||
"ShareBoard.confirmRegenerateToken": "This will invalidate previously shared links. Continue?",
|
||||
"ShareBoard.copiedLink": "Copied!",
|
||||
"ShareBoard.copyLink": "Copy link",
|
||||
"ShareBoard.regenerate": "Regenerate token",
|
||||
"ShareBoard.regenerateToken": "Regenerate token",
|
||||
"ShareBoard.share": "Publish and share this board with anyone who has the link",
|
||||
"ShareBoard.tokenRegenrated": "Token regenerated",
|
||||
@ -232,6 +237,7 @@
|
||||
"ViewHeader.share-board": "Share board",
|
||||
"ViewHeader.sort": "Sort",
|
||||
"ViewHeader.untitled": "Untitled",
|
||||
"ViewHeader.view-header-menu": "View header menu",
|
||||
"ViewHeader.view-menu": "View menu",
|
||||
"ViewTitle.hide-description": "hide description",
|
||||
"ViewTitle.pick-icon": "Pick icon",
|
||||
|
@ -42,7 +42,7 @@ declare let window: IAppWindow
|
||||
|
||||
const UUID_REGEX = new RegExp(/^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/)
|
||||
|
||||
const App = React.memo((): JSX.Element => {
|
||||
const App = (): JSX.Element => {
|
||||
const language = useAppSelector<string>(getLanguage)
|
||||
const loggedIn = useAppSelector<boolean|null>(getLoggedIn)
|
||||
const globalError = useAppSelector<string>(getGlobalError)
|
||||
@ -277,6 +277,6 @@ const App = React.memo((): JSX.Element => {
|
||||
</DndProvider>
|
||||
</IntlProvider>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default App
|
||||
export default React.memo(App)
|
||||
|
@ -67,6 +67,7 @@ function createBoard(block?: Block): Board {
|
||||
description: block?.fields.description || '',
|
||||
icon: block?.fields.icon || '',
|
||||
isTemplate: block?.fields.isTemplate || false,
|
||||
templateVer: block?.fields.templateVer || 0,
|
||||
columnCalculations: block?.fields.columnCalculations || [],
|
||||
cardProperties,
|
||||
},
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,601 +0,0 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`src/components/shareBoardComponent return shareBoardComponent and click Copy link 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Modal bottom"
|
||||
>
|
||||
<div
|
||||
class="toolbar hideOnWidescreen"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
title="Close"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ShareBoardComponent"
|
||||
>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div>
|
||||
Anyone with the link can view this board and all cards in it.
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/workspace/1/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/workspace/1/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Copied!
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Regenerate token
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoardComponent return shareBoardComponent and click Regenerate token 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Modal bottom"
|
||||
>
|
||||
<div
|
||||
class="toolbar hideOnWidescreen"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
title="Close"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ShareBoardComponent"
|
||||
>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div>
|
||||
Anyone with the link can view this board and all cards in it.
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/workspace/1/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/workspace/1/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Regenerate token
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoardComponent return shareBoardComponent and click Switch 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Modal bottom"
|
||||
>
|
||||
<div
|
||||
class="toolbar hideOnWidescreen"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
title="Close"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ShareBoardComponent"
|
||||
>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div>
|
||||
Anyone with the link can view this board and all cards in it.
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/workspace/1/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/workspace/1/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Regenerate token
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoardComponent return shareBoardComponent and click Switch without sharing 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Modal bottom"
|
||||
>
|
||||
<div
|
||||
class="toolbar hideOnWidescreen"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
title="Close"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ShareBoardComponent"
|
||||
>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div>
|
||||
Anyone with the link can view this board and all cards in it.
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/workspace/1/shared/1/1?r=aToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/workspace/1/shared/1/1?r=aToken
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Regenerate token
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoardComponent should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Modal bottom"
|
||||
>
|
||||
<div
|
||||
class="toolbar hideOnWidescreen"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
title="Close"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ShareBoardComponent"
|
||||
>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div>
|
||||
Publish and share this board with anyone who has the link
|
||||
</div>
|
||||
<div
|
||||
class="Switch"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoardComponent should match snapshot with sharing 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Modal bottom"
|
||||
>
|
||||
<div
|
||||
class="toolbar hideOnWidescreen"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
title="Close"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ShareBoardComponent"
|
||||
>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div>
|
||||
Anyone with the link can view this board and all cards in it.
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/workspace/1/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/workspace/1/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Regenerate token
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoardComponent should match snapshot with sharing and subpath 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Modal bottom"
|
||||
>
|
||||
<div
|
||||
class="toolbar hideOnWidescreen"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
title="Close"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ShareBoardComponent"
|
||||
>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div>
|
||||
Anyone with the link can view this board and all cards in it.
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/test-subpath/plugins/boards/workspace/1/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/test-subpath/plugins/boards/workspace/1/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Regenerate token
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoardComponent should match snapshot with sharing and without workspaceId 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Modal bottom"
|
||||
>
|
||||
<div
|
||||
class="toolbar hideOnWidescreen"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
title="Close"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ShareBoardComponent"
|
||||
>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div>
|
||||
Anyone with the link can view this board and all cards in it.
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Regenerate token
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoardComponent should match snapshot with sharing and without workspaceId and subpath 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Modal bottom"
|
||||
>
|
||||
<div
|
||||
class="toolbar hideOnWidescreen"
|
||||
>
|
||||
<button
|
||||
aria-label="Close"
|
||||
title="Close"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="ShareBoardComponent"
|
||||
>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<div>
|
||||
Anyone with the link can view this board and all cards in it.
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/test-subpath/plugins/boards/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/test-subpath/plugins/boards/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="row"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Regenerate token
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -250,106 +250,110 @@ exports[`src/components/workspace return workspace and showcard 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="ViewTitle"
|
||||
class="mid-head"
|
||||
>
|
||||
<div
|
||||
class="add-buttons add-visible"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
class="HideIcon Icon"
|
||||
viewBox="0 0 640 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
hide description
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="title"
|
||||
class="ViewTitle"
|
||||
>
|
||||
<div
|
||||
class="BlockIconSelector"
|
||||
class="add-buttons add-visible"
|
||||
>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="octo-icon size-m"
|
||||
<svg
|
||||
class="HideIcon Icon"
|
||||
viewBox="0 0 640 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<span>
|
||||
i
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<path
|
||||
d="M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
hide description
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
class="Editable title"
|
||||
placeholder="Untitled board"
|
||||
spellcheck="true"
|
||||
title="board title"
|
||||
value="board title"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="description"
|
||||
>
|
||||
<div
|
||||
class="MarkdownEditor octo-editor "
|
||||
class="title"
|
||||
>
|
||||
<div
|
||||
class="octo-editor-preview"
|
||||
data-testid="preview-element"
|
||||
/>
|
||||
<div
|
||||
class="MarkdownEditorInput MarkdownEditorInput--IsNotEditing"
|
||||
class="BlockIconSelector"
|
||||
>
|
||||
<div
|
||||
class="DraftEditor-root"
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="DraftEditor-editorContainer"
|
||||
class="octo-icon size-m"
|
||||
>
|
||||
<span>
|
||||
i
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="Editable title"
|
||||
placeholder="Untitled board"
|
||||
spellcheck="true"
|
||||
title="board title"
|
||||
value="board title"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="description"
|
||||
>
|
||||
<div
|
||||
class="MarkdownEditor octo-editor "
|
||||
>
|
||||
<div
|
||||
class="octo-editor-preview"
|
||||
data-testid="preview-element"
|
||||
/>
|
||||
<div
|
||||
class="MarkdownEditorInput MarkdownEditorInput--IsNotEditing"
|
||||
>
|
||||
<div
|
||||
class="DraftEditor-root"
|
||||
>
|
||||
<div
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="false"
|
||||
class="notranslate public-DraftEditor-content"
|
||||
contenteditable="true"
|
||||
role="combobox"
|
||||
spellcheck="false"
|
||||
style="outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;"
|
||||
class="DraftEditor-editorContainer"
|
||||
>
|
||||
<div
|
||||
data-contents="true"
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="false"
|
||||
class="notranslate public-DraftEditor-content"
|
||||
contenteditable="true"
|
||||
role="combobox"
|
||||
spellcheck="false"
|
||||
style="outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
data-block="true"
|
||||
data-editor="123"
|
||||
data-offset-key="123-0-0"
|
||||
data-contents="true"
|
||||
>
|
||||
<div
|
||||
class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr"
|
||||
class=""
|
||||
data-block="true"
|
||||
data-editor="123"
|
||||
data-offset-key="123-0-0"
|
||||
>
|
||||
<span
|
||||
<div
|
||||
class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr"
|
||||
data-offset-key="123-0-0"
|
||||
>
|
||||
<span
|
||||
data-text="true"
|
||||
data-offset-key="123-0-0"
|
||||
>
|
||||
description
|
||||
<span
|
||||
data-text="true"
|
||||
>
|
||||
description
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -359,6 +363,21 @@ exports[`src/components/workspace return workspace and showcard 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ShareBoardButton"
|
||||
>
|
||||
<button
|
||||
title="Share board"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="CompassIcon icon-globe CompassIcon"
|
||||
/>
|
||||
Share
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ViewHeader"
|
||||
@ -807,90 +826,94 @@ exports[`src/components/workspace return workspace readonly and showcard 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="ViewTitle"
|
||||
class="mid-head"
|
||||
>
|
||||
<div
|
||||
class="add-buttons add-visible"
|
||||
/>
|
||||
<div
|
||||
class="title"
|
||||
class="ViewTitle"
|
||||
>
|
||||
<div
|
||||
class="BlockIconSelector"
|
||||
>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="octo-icon size-m"
|
||||
>
|
||||
<span>
|
||||
i
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="Editable readonly title"
|
||||
placeholder="Untitled board"
|
||||
readonly=""
|
||||
spellcheck="true"
|
||||
title="board title"
|
||||
value="board title"
|
||||
class="add-buttons add-visible"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="description"
|
||||
>
|
||||
<div
|
||||
class="MarkdownEditor octo-editor "
|
||||
class="title"
|
||||
>
|
||||
<div
|
||||
class="octo-editor-preview"
|
||||
data-testid="preview-element"
|
||||
/>
|
||||
<div
|
||||
class="MarkdownEditorInput MarkdownEditorInput--IsNotEditing"
|
||||
class="BlockIconSelector"
|
||||
>
|
||||
<div
|
||||
class="DraftEditor-root"
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="DraftEditor-editorContainer"
|
||||
class="octo-icon size-m"
|
||||
>
|
||||
<span>
|
||||
i
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="Editable readonly title"
|
||||
placeholder="Untitled board"
|
||||
readonly=""
|
||||
spellcheck="true"
|
||||
title="board title"
|
||||
value="board title"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="description"
|
||||
>
|
||||
<div
|
||||
class="MarkdownEditor octo-editor "
|
||||
>
|
||||
<div
|
||||
class="octo-editor-preview"
|
||||
data-testid="preview-element"
|
||||
/>
|
||||
<div
|
||||
class="MarkdownEditorInput MarkdownEditorInput--IsNotEditing"
|
||||
>
|
||||
<div
|
||||
class="DraftEditor-root"
|
||||
>
|
||||
<div
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="false"
|
||||
class="notranslate public-DraftEditor-content"
|
||||
contenteditable="true"
|
||||
role="combobox"
|
||||
spellcheck="false"
|
||||
style="outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;"
|
||||
class="DraftEditor-editorContainer"
|
||||
>
|
||||
<div
|
||||
data-contents="true"
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="false"
|
||||
class="notranslate public-DraftEditor-content"
|
||||
contenteditable="true"
|
||||
role="combobox"
|
||||
spellcheck="false"
|
||||
style="outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
data-block="true"
|
||||
data-editor="123"
|
||||
data-offset-key="123-0-0"
|
||||
data-contents="true"
|
||||
>
|
||||
<div
|
||||
class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr"
|
||||
class=""
|
||||
data-block="true"
|
||||
data-editor="123"
|
||||
data-offset-key="123-0-0"
|
||||
>
|
||||
<span
|
||||
<div
|
||||
class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr"
|
||||
data-offset-key="123-0-0"
|
||||
>
|
||||
<span
|
||||
data-text="true"
|
||||
data-offset-key="123-0-0"
|
||||
>
|
||||
description
|
||||
<span
|
||||
data-text="true"
|
||||
>
|
||||
description
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1451,106 +1474,110 @@ exports[`src/components/workspace should match snapshot 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="ViewTitle"
|
||||
class="mid-head"
|
||||
>
|
||||
<div
|
||||
class="add-buttons add-visible"
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<svg
|
||||
class="HideIcon Icon"
|
||||
viewBox="0 0 640 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
hide description
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="title"
|
||||
class="ViewTitle"
|
||||
>
|
||||
<div
|
||||
class="BlockIconSelector"
|
||||
class="add-buttons add-visible"
|
||||
>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
<button
|
||||
type="button"
|
||||
>
|
||||
<div
|
||||
class="octo-icon size-m"
|
||||
<svg
|
||||
class="HideIcon Icon"
|
||||
viewBox="0 0 640 512"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<span>
|
||||
i
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<path
|
||||
d="M634 471L36 3.51A16 16 0 0 0 13.51 6l-10 12.49A16 16 0 0 0 6 41l598 467.49a16 16 0 0 0 22.49-2.49l10-12.49A16 16 0 0 0 634 471zM296.79 146.47l134.79 105.38C429.36 191.91 380.48 144 320 144a112.26 112.26 0 0 0-23.21 2.47zm46.42 219.07L208.42 260.16C210.65 320.09 259.53 368 320 368a113 113 0 0 0 23.21-2.46zM320 112c98.65 0 189.09 55 237.93 144a285.53 285.53 0 0 1-44 60.2l37.74 29.5a333.7 333.7 0 0 0 52.9-75.11 32.35 32.35 0 0 0 0-29.19C550.29 135.59 442.93 64 320 64c-36.7 0-71.71 7-104.63 18.81l46.41 36.29c18.94-4.3 38.34-7.1 58.22-7.1zm0 288c-98.65 0-189.08-55-237.93-144a285.47 285.47 0 0 1 44.05-60.19l-37.74-29.5a333.6 333.6 0 0 0-52.89 75.1 32.35 32.35 0 0 0 0 29.19C89.72 376.41 197.08 448 320 448c36.7 0 71.71-7.05 104.63-18.81l-46.41-36.28C359.28 397.2 339.89 400 320 400z"
|
||||
/>
|
||||
</svg>
|
||||
<span>
|
||||
hide description
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<input
|
||||
class="Editable title"
|
||||
placeholder="Untitled board"
|
||||
spellcheck="true"
|
||||
title="board title"
|
||||
value="board title"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="description"
|
||||
>
|
||||
<div
|
||||
class="MarkdownEditor octo-editor "
|
||||
class="title"
|
||||
>
|
||||
<div
|
||||
class="octo-editor-preview"
|
||||
data-testid="preview-element"
|
||||
/>
|
||||
<div
|
||||
class="MarkdownEditorInput MarkdownEditorInput--IsNotEditing"
|
||||
class="BlockIconSelector"
|
||||
>
|
||||
<div
|
||||
class="DraftEditor-root"
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="DraftEditor-editorContainer"
|
||||
class="octo-icon size-m"
|
||||
>
|
||||
<span>
|
||||
i
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="Editable title"
|
||||
placeholder="Untitled board"
|
||||
spellcheck="true"
|
||||
title="board title"
|
||||
value="board title"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="description"
|
||||
>
|
||||
<div
|
||||
class="MarkdownEditor octo-editor "
|
||||
>
|
||||
<div
|
||||
class="octo-editor-preview"
|
||||
data-testid="preview-element"
|
||||
/>
|
||||
<div
|
||||
class="MarkdownEditorInput MarkdownEditorInput--IsNotEditing"
|
||||
>
|
||||
<div
|
||||
class="DraftEditor-root"
|
||||
>
|
||||
<div
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="false"
|
||||
class="notranslate public-DraftEditor-content"
|
||||
contenteditable="true"
|
||||
role="combobox"
|
||||
spellcheck="false"
|
||||
style="outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;"
|
||||
class="DraftEditor-editorContainer"
|
||||
>
|
||||
<div
|
||||
data-contents="true"
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="false"
|
||||
class="notranslate public-DraftEditor-content"
|
||||
contenteditable="true"
|
||||
role="combobox"
|
||||
spellcheck="false"
|
||||
style="outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
data-block="true"
|
||||
data-editor="123"
|
||||
data-offset-key="123-0-0"
|
||||
data-contents="true"
|
||||
>
|
||||
<div
|
||||
class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr"
|
||||
class=""
|
||||
data-block="true"
|
||||
data-editor="123"
|
||||
data-offset-key="123-0-0"
|
||||
>
|
||||
<span
|
||||
<div
|
||||
class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr"
|
||||
data-offset-key="123-0-0"
|
||||
>
|
||||
<span
|
||||
data-text="true"
|
||||
data-offset-key="123-0-0"
|
||||
>
|
||||
description
|
||||
<span
|
||||
data-text="true"
|
||||
>
|
||||
description
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -1560,6 +1587,21 @@ exports[`src/components/workspace should match snapshot 1`] = `
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ShareBoardButton"
|
||||
>
|
||||
<button
|
||||
title="Share board"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="CompassIcon icon-globe CompassIcon"
|
||||
/>
|
||||
Share
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="ViewHeader"
|
||||
@ -2008,90 +2050,94 @@ exports[`src/components/workspace should match snapshot with readonly 1`] = `
|
||||
</a>
|
||||
</div>
|
||||
<div
|
||||
class="ViewTitle"
|
||||
class="mid-head"
|
||||
>
|
||||
<div
|
||||
class="add-buttons add-visible"
|
||||
/>
|
||||
<div
|
||||
class="title"
|
||||
class="ViewTitle"
|
||||
>
|
||||
<div
|
||||
class="BlockIconSelector"
|
||||
>
|
||||
<div
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="octo-icon size-m"
|
||||
>
|
||||
<span>
|
||||
i
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="Editable readonly title"
|
||||
placeholder="Untitled board"
|
||||
readonly=""
|
||||
spellcheck="true"
|
||||
title="board title"
|
||||
value="board title"
|
||||
class="add-buttons add-visible"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="description"
|
||||
>
|
||||
<div
|
||||
class="MarkdownEditor octo-editor "
|
||||
class="title"
|
||||
>
|
||||
<div
|
||||
class="octo-editor-preview"
|
||||
data-testid="preview-element"
|
||||
/>
|
||||
<div
|
||||
class="MarkdownEditorInput MarkdownEditorInput--IsNotEditing"
|
||||
class="BlockIconSelector"
|
||||
>
|
||||
<div
|
||||
class="DraftEditor-root"
|
||||
aria-label="menuwrapper"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="DraftEditor-editorContainer"
|
||||
class="octo-icon size-m"
|
||||
>
|
||||
<span>
|
||||
i
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input
|
||||
class="Editable readonly title"
|
||||
placeholder="Untitled board"
|
||||
readonly=""
|
||||
spellcheck="true"
|
||||
title="board title"
|
||||
value="board title"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
class="description"
|
||||
>
|
||||
<div
|
||||
class="MarkdownEditor octo-editor "
|
||||
>
|
||||
<div
|
||||
class="octo-editor-preview"
|
||||
data-testid="preview-element"
|
||||
/>
|
||||
<div
|
||||
class="MarkdownEditorInput MarkdownEditorInput--IsNotEditing"
|
||||
>
|
||||
<div
|
||||
class="DraftEditor-root"
|
||||
>
|
||||
<div
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="false"
|
||||
class="notranslate public-DraftEditor-content"
|
||||
contenteditable="true"
|
||||
role="combobox"
|
||||
spellcheck="false"
|
||||
style="outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;"
|
||||
class="DraftEditor-editorContainer"
|
||||
>
|
||||
<div
|
||||
data-contents="true"
|
||||
aria-autocomplete="list"
|
||||
aria-expanded="false"
|
||||
class="notranslate public-DraftEditor-content"
|
||||
contenteditable="true"
|
||||
role="combobox"
|
||||
spellcheck="false"
|
||||
style="outline: none; user-select: text; white-space: pre-wrap; word-wrap: break-word;"
|
||||
>
|
||||
<div
|
||||
class=""
|
||||
data-block="true"
|
||||
data-editor="123"
|
||||
data-offset-key="123-0-0"
|
||||
data-contents="true"
|
||||
>
|
||||
<div
|
||||
class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr"
|
||||
class=""
|
||||
data-block="true"
|
||||
data-editor="123"
|
||||
data-offset-key="123-0-0"
|
||||
>
|
||||
<span
|
||||
<div
|
||||
class="public-DraftStyleDefault-block public-DraftStyleDefault-ltr"
|
||||
data-offset-key="123-0-0"
|
||||
>
|
||||
<span
|
||||
data-text="true"
|
||||
data-offset-key="123-0-0"
|
||||
>
|
||||
description
|
||||
<span
|
||||
data-text="true"
|
||||
>
|
||||
description
|
||||
</span>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,7 +18,7 @@ type Props = {
|
||||
cords: {x: number, y?: number, z?: number}
|
||||
}
|
||||
|
||||
const AddContentMenuItem = React.memo((props:Props): JSX.Element => {
|
||||
const AddContentMenuItem = (props:Props): JSX.Element => {
|
||||
const {card, type, cords} = props
|
||||
const index = cords.x
|
||||
const contentOrder = card.fields.contentOrder.slice()
|
||||
@ -51,6 +51,6 @@ const AddContentMenuItem = React.memo((props:Props): JSX.Element => {
|
||||
}}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default AddContentMenuItem
|
||||
export default React.memo(AddContentMenuItem)
|
||||
|
@ -20,7 +20,7 @@ type Props = {
|
||||
readonly?: boolean
|
||||
}
|
||||
|
||||
const BlockIconSelector = React.memo((props: Props) => {
|
||||
const BlockIconSelector = (props: Props) => {
|
||||
const {block, size} = props
|
||||
const intl = useIntl()
|
||||
|
||||
@ -72,6 +72,6 @@ const BlockIconSelector = React.memo((props: Props) => {
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default BlockIconSelector
|
||||
export default React.memo(BlockIconSelector)
|
||||
|
@ -110,11 +110,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plu
|
||||
>
|
||||
<div
|
||||
class="BoardTemplateSelectorPreview"
|
||||
>
|
||||
<div
|
||||
class="prevent-click"
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
<div
|
||||
class="buttons"
|
||||
>
|
||||
@ -239,11 +235,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plu
|
||||
>
|
||||
<div
|
||||
class="BoardTemplateSelectorPreview"
|
||||
>
|
||||
<div
|
||||
class="prevent-click"
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
<div
|
||||
class="buttons"
|
||||
>
|
||||
@ -368,11 +360,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelector a focalboard Plu
|
||||
>
|
||||
<div
|
||||
class="BoardTemplateSelectorPreview"
|
||||
>
|
||||
<div
|
||||
class="prevent-click"
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
<div
|
||||
class="buttons"
|
||||
>
|
||||
@ -507,11 +495,7 @@ exports[`components/boardTemplateSelector/boardTemplateSelector not a focalboard
|
||||
>
|
||||
<div
|
||||
class="BoardTemplateSelectorPreview"
|
||||
>
|
||||
<div
|
||||
class="prevent-click"
|
||||
/>
|
||||
</div>
|
||||
/>
|
||||
<div
|
||||
class="buttons"
|
||||
>
|
||||
|
@ -7,9 +7,6 @@ exports[`components/boardTemplateSelector/boardTemplateSelectorPreview should ma
|
||||
<div
|
||||
class="BoardTemplateSelectorPreview"
|
||||
>
|
||||
<div
|
||||
class="prevent-click"
|
||||
/>
|
||||
<div
|
||||
class="top-head"
|
||||
>
|
||||
|
@ -41,7 +41,7 @@
|
||||
|
||||
.templates-list {
|
||||
margin-right: 32px;
|
||||
min-width: 200px;
|
||||
width: 300px;
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
|
||||
@ -82,6 +82,10 @@
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty-board {
|
||||
background-color: rgb(var(--center-channel-bg-rgb));
|
||||
}
|
||||
|
||||
.Button {
|
||||
&:first-child {
|
||||
margin-right: 16px;
|
||||
|
@ -100,6 +100,8 @@ describe('components/boardTemplateSelector/boardTemplateSelector', () => {
|
||||
{id: 'global-id-5'},
|
||||
],
|
||||
dateDisplayPropertyId: 'global-id-5',
|
||||
isTemplate: true,
|
||||
templateVer: 2,
|
||||
},
|
||||
}],
|
||||
},
|
||||
|
@ -27,7 +27,7 @@ type Props = {
|
||||
onClose?: () => void
|
||||
}
|
||||
|
||||
const BoardTemplateSelector = React.memo((props: Props) => {
|
||||
const BoardTemplateSelector = (props: Props) => {
|
||||
const globalTemplates = useAppSelector<Board[]>(getGlobalTemplates) || []
|
||||
const currentBoard = useAppSelector<Board>(getCurrentBoard) || null
|
||||
const {title, description, onClose} = props
|
||||
@ -166,7 +166,6 @@ const BoardTemplateSelector = React.memo((props: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
|
||||
export default BoardTemplateSelector
|
||||
}
|
||||
|
||||
export default React.memo(BoardTemplateSelector)
|
||||
|
@ -40,6 +40,10 @@
|
||||
position: relative;
|
||||
display: none;
|
||||
right: -8px;
|
||||
|
||||
.DeleteIcon {
|
||||
padding-top: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -127,6 +127,8 @@ describe('components/boardTemplateSelector/boardTemplateSelectorItem', () => {
|
||||
cardProperties: [groupProperty],
|
||||
dateDisplayPropertyId: 'global-id-5',
|
||||
columnCalculations: {},
|
||||
isTemplate: true,
|
||||
templateVer: 2,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ type Props = {
|
||||
onEdit: (templateId: string) => void
|
||||
}
|
||||
|
||||
const BoardTemplateSelectorItem = React.memo((props: Props) => {
|
||||
const BoardTemplateSelectorItem = (props: Props) => {
|
||||
const {isActive, template, onEdit, onDelete, onSelect} = props
|
||||
const intl = useIntl()
|
||||
const [deleteOpen, setDeleteOpen] = useState<boolean>(false)
|
||||
@ -38,7 +38,7 @@ const BoardTemplateSelectorItem = React.memo((props: Props) => {
|
||||
>
|
||||
<span className='template-icon'>{template.fields.icon}</span>
|
||||
<span className='template-name'>{template.title}</span>
|
||||
{template.workspaceId !== '0' &&
|
||||
{!template.fields.templateVer &&
|
||||
<div className='actions'>
|
||||
<IconButton
|
||||
icon={<DeleteIcon/>}
|
||||
@ -65,6 +65,6 @@ const BoardTemplateSelectorItem = React.memo((props: Props) => {
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default BoardTemplateSelectorItem
|
||||
export default React.memo(BoardTemplateSelectorItem)
|
||||
|
@ -4,15 +4,9 @@
|
||||
width: 158%;
|
||||
height: 480px;
|
||||
border-radius: var(--modal-rad);
|
||||
pointer-events: none;
|
||||
|
||||
.Kanban {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.prevent-click {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 1100;
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ type Props = {
|
||||
activeTemplate: Board|null
|
||||
}
|
||||
|
||||
const BoardTemplateSelectorPreview = React.memo((props: Props) => {
|
||||
const BoardTemplateSelectorPreview = (props: Props) => {
|
||||
const {activeTemplate} = props
|
||||
const [activeView, setActiveView] = useState<BoardView|null>(null)
|
||||
const [activeTemplateCards, setActiveTemplateCards] = useState<Card[]>([])
|
||||
@ -33,7 +33,7 @@ const BoardTemplateSelectorPreview = React.memo((props: Props) => {
|
||||
setActiveTemplateCards([])
|
||||
octoClient.getSubtree(activeTemplate.id, activeView?.fields.viewType === 'gallery' ? 3 : 2, activeTemplate.workspaceId).then((blocks) => {
|
||||
const cards = blocks.filter((b) => b.type === 'card')
|
||||
const views = blocks.filter((b) => b.type === 'view')
|
||||
const views = blocks.filter((b) => b.type === 'view').sort((a, b) => a.title.localeCompare(b.title))
|
||||
if (views.length > 0) {
|
||||
setActiveView(views[0] as BoardView)
|
||||
}
|
||||
@ -65,28 +65,26 @@ const BoardTemplateSelectorPreview = React.memo((props: Props) => {
|
||||
|
||||
return (
|
||||
<div className='BoardTemplateSelectorPreview'>
|
||||
<div className='prevent-click'/>
|
||||
{activeView &&
|
||||
<div className='top-head'>
|
||||
<ViewTitle
|
||||
key={activeTemplate?.id + activeTemplate?.title}
|
||||
board={activeTemplate}
|
||||
readonly={true}
|
||||
/>
|
||||
<ViewHeader
|
||||
board={activeTemplate}
|
||||
activeView={activeView}
|
||||
cards={activeTemplateCards}
|
||||
views={[activeView]}
|
||||
groupByProperty={groupByProperty}
|
||||
addCard={() => null}
|
||||
addCardFromTemplate={() => null}
|
||||
addCardTemplate={() => null}
|
||||
editCardTemplate={() => null}
|
||||
readonly={false}
|
||||
showShared={false}
|
||||
/>
|
||||
</div>}
|
||||
<div className='top-head'>
|
||||
<ViewTitle
|
||||
key={activeTemplate?.id + activeTemplate?.title}
|
||||
board={activeTemplate}
|
||||
readonly={true}
|
||||
/>
|
||||
<ViewHeader
|
||||
board={activeTemplate}
|
||||
activeView={activeView}
|
||||
cards={activeTemplateCards}
|
||||
views={[activeView]}
|
||||
groupByProperty={groupByProperty}
|
||||
addCard={() => null}
|
||||
addCardFromTemplate={() => null}
|
||||
addCardTemplate={() => null}
|
||||
editCardTemplate={() => null}
|
||||
readonly={false}
|
||||
/>
|
||||
</div>}
|
||||
|
||||
{activeView?.fields.viewType === 'board' &&
|
||||
<Kanban
|
||||
@ -103,43 +101,43 @@ const BoardTemplateSelectorPreview = React.memo((props: Props) => {
|
||||
showCard={() => null}
|
||||
/>}
|
||||
{activeView?.fields.viewType === 'table' &&
|
||||
<Table
|
||||
board={activeTemplate}
|
||||
activeView={activeView}
|
||||
cards={activeTemplateCards}
|
||||
groupByProperty={groupByProperty}
|
||||
views={[activeView]}
|
||||
visibleGroups={visibleGroups}
|
||||
selectedCardIds={[]}
|
||||
readonly={false}
|
||||
cardIdToFocusOnRender={''}
|
||||
onCardClicked={() => null}
|
||||
addCard={() => Promise.resolve()}
|
||||
showCard={() => null}
|
||||
/>}
|
||||
<Table
|
||||
board={activeTemplate}
|
||||
activeView={activeView}
|
||||
cards={activeTemplateCards}
|
||||
groupByProperty={groupByProperty}
|
||||
views={[activeView]}
|
||||
visibleGroups={visibleGroups}
|
||||
selectedCardIds={[]}
|
||||
readonly={false}
|
||||
cardIdToFocusOnRender={''}
|
||||
onCardClicked={() => null}
|
||||
addCard={() => Promise.resolve()}
|
||||
showCard={() => null}
|
||||
/>}
|
||||
{activeView?.fields.viewType === 'gallery' &&
|
||||
<Gallery
|
||||
board={activeTemplate}
|
||||
cards={activeTemplateCards}
|
||||
activeView={activeView}
|
||||
readonly={false}
|
||||
selectedCardIds={[]}
|
||||
onCardClicked={() => null}
|
||||
addCard={() => Promise.resolve()}
|
||||
/>}
|
||||
<Gallery
|
||||
board={activeTemplate}
|
||||
cards={activeTemplateCards}
|
||||
activeView={activeView}
|
||||
readonly={false}
|
||||
selectedCardIds={[]}
|
||||
onCardClicked={() => null}
|
||||
addCard={() => Promise.resolve()}
|
||||
/>}
|
||||
{activeView?.fields.viewType === 'calendar' &&
|
||||
<CalendarFullView
|
||||
board={activeTemplate}
|
||||
cards={activeTemplateCards}
|
||||
activeView={activeView}
|
||||
readonly={false}
|
||||
dateDisplayProperty={dateDisplayProperty}
|
||||
showCard={() => null}
|
||||
addCard={() => Promise.resolve()}
|
||||
/>}
|
||||
<CalendarFullView
|
||||
board={activeTemplate}
|
||||
cards={activeTemplateCards}
|
||||
activeView={activeView}
|
||||
readonly={false}
|
||||
dateDisplayProperty={dateDisplayProperty}
|
||||
showCard={() => null}
|
||||
addCard={() => Promise.resolve()}
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default BoardTemplateSelectorPreview
|
||||
export default React.memo(BoardTemplateSelectorPreview)
|
||||
|
||||
|
@ -147,7 +147,7 @@ const ContentBlockWithDragAndDrop = (props: ContentBlockWithDragAndDropProps) =>
|
||||
)
|
||||
}
|
||||
|
||||
const CardDetailContents = React.memo((props: Props) => {
|
||||
const CardDetailContents = (props: Props) => {
|
||||
const intl = useIntl()
|
||||
const {contents, card, id} = props
|
||||
if (contents.length) {
|
||||
@ -188,6 +188,6 @@ const CardDetailContents = React.memo((props: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default CardDetailContents
|
||||
export default React.memo(CardDetailContents)
|
||||
|
@ -37,7 +37,7 @@ function addContentMenu(intl: IntlShape, type: BlockTypes): JSX.Element {
|
||||
)
|
||||
}
|
||||
|
||||
const CardDetailContentsMenu = React.memo(() => {
|
||||
const CardDetailContentsMenu = () => {
|
||||
const intl = useIntl()
|
||||
return (
|
||||
<div className='CardDetailContentsMenu content add-content'>
|
||||
@ -54,6 +54,6 @@ const CardDetailContentsMenu = React.memo(() => {
|
||||
</MenuWrapper>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default CardDetailContentsMenu
|
||||
export default React.memo(CardDetailContentsMenu)
|
||||
|
@ -32,7 +32,7 @@ type Props = {
|
||||
readonly: boolean
|
||||
}
|
||||
|
||||
const CardDetailProperties = React.memo((props: Props) => {
|
||||
const CardDetailProperties = (props: Props) => {
|
||||
const {board, card, cards, views, activeView, contents, comments} = props
|
||||
const [newTemplateId, setNewTemplateId] = useState('')
|
||||
const intl = useIntl()
|
||||
@ -202,6 +202,6 @@ const CardDetailProperties = React.memo((props: Props) => {
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default CardDetailProperties
|
||||
export default React.memo(CardDetailProperties)
|
||||
|
@ -24,7 +24,7 @@ type Props = {
|
||||
readonly: boolean
|
||||
}
|
||||
|
||||
const CommentsList = React.memo((props: Props) => {
|
||||
const CommentsList = (props: Props) => {
|
||||
const [newComment, setNewComment] = useState('')
|
||||
const me = useAppSelector<IUser|null>(getMe)
|
||||
|
||||
@ -97,6 +97,6 @@ const CommentsList = React.memo((props: Props) => {
|
||||
{!(comments.length === 0 && props.readonly) && <hr className='CommentsList__divider'/>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default CommentsList
|
||||
export default React.memo(CommentsList)
|
||||
|
@ -44,7 +44,15 @@
|
||||
left: 0;
|
||||
background: rgb(var(--center-channel-bg-rgb));
|
||||
z-index: 100;
|
||||
|
||||
> .mid-head {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
> div:nth-child(2) {
|
||||
padding: 0 0 0 1px;
|
||||
|
@ -110,6 +110,24 @@ describe('components/centerPanel', () => {
|
||||
activeView.fields.viewType = 'board'
|
||||
jest.clearAllMocks()
|
||||
})
|
||||
test('should match snapshot for Kanban, not shared', () => {
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
<CenterPanel
|
||||
cards={[card1]}
|
||||
views={[activeView]}
|
||||
board={board}
|
||||
activeView={activeView}
|
||||
readonly={false}
|
||||
showCard={jest.fn()}
|
||||
showShared={false}
|
||||
groupByProperty={groupProperty}
|
||||
shownCardId={card1.id}
|
||||
/>
|
||||
</ReduxProvider>,
|
||||
))
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('should match snapshot for Kanban', () => {
|
||||
const {container} = render(wrapDNDIntl(
|
||||
<ReduxProvider store={store}>
|
||||
|
@ -20,11 +20,12 @@ import {UserSettings} from '../userSettings'
|
||||
import {addCard, addTemplate} from '../store/cards'
|
||||
import {updateView} from '../store/views'
|
||||
import {getVisibleAndHiddenGroups} from '../boardUtils'
|
||||
import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../../webapp/src/telemetry/telemetryClient'
|
||||
|
||||
import ShareBoardButton from './shareBoard/shareBoardButton'
|
||||
|
||||
import './centerPanel.scss'
|
||||
|
||||
import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../../webapp/src/telemetry/telemetryClient'
|
||||
|
||||
import CardDialog from './cardDialog'
|
||||
import RootPortal from './rootPortal'
|
||||
import TopBar from './topBar'
|
||||
@ -59,6 +60,7 @@ type Props = {
|
||||
type State = {
|
||||
selectedCardIds: string[]
|
||||
cardIdToFocusOnRender: string
|
||||
showShareDialog: boolean
|
||||
}
|
||||
|
||||
class CenterPanel extends React.Component<Props, State> {
|
||||
@ -102,6 +104,7 @@ class CenterPanel extends React.Component<Props, State> {
|
||||
this.state = {
|
||||
selectedCardIds: [],
|
||||
cardIdToFocusOnRender: '',
|
||||
showShareDialog: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -146,11 +149,18 @@ class CenterPanel extends React.Component<Props, State> {
|
||||
|
||||
<div className='top-head'>
|
||||
<TopBar/>
|
||||
<ViewTitle
|
||||
key={board.id + board.title}
|
||||
board={board}
|
||||
readonly={this.props.readonly}
|
||||
/>
|
||||
<div className='mid-head'>
|
||||
<ViewTitle
|
||||
key={board.id + board.title}
|
||||
board={board}
|
||||
readonly={this.props.readonly}
|
||||
/>
|
||||
{!this.props.readonly && this.props.showShared &&
|
||||
<ShareBoardButton
|
||||
boardId={this.props.board.id}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<ViewHeader
|
||||
board={this.props.board}
|
||||
activeView={this.props.activeView}
|
||||
@ -163,7 +173,6 @@ class CenterPanel extends React.Component<Props, State> {
|
||||
addCardTemplate={this.addCardTemplate}
|
||||
editCardTemplate={this.editCardTemplate}
|
||||
readonly={this.props.readonly}
|
||||
showShared={this.props.showShared}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
@ -21,7 +21,7 @@ type Props = {
|
||||
onDeleteElement?: () => void
|
||||
}
|
||||
|
||||
const CheckboxElement = React.memo((props: Props) => {
|
||||
const CheckboxElement = (props: Props) => {
|
||||
const {block, readonly} = props
|
||||
const intl = useIntl()
|
||||
const titleRef = useRef<Focusable>(null)
|
||||
@ -83,7 +83,7 @@ const CheckboxElement = React.memo((props: Props) => {
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
contentRegistry.registerContentType({
|
||||
type: 'checkbox',
|
||||
@ -104,4 +104,4 @@ contentRegistry.registerContentType({
|
||||
},
|
||||
})
|
||||
|
||||
export default CheckboxElement
|
||||
export default React.memo(CheckboxElement)
|
||||
|
@ -8,7 +8,7 @@ import DividerIcon from '../../widgets/icons/divider'
|
||||
import {contentRegistry} from './contentRegistry'
|
||||
import './dividerElement.scss'
|
||||
|
||||
const DividerElement = React.memo((): JSX.Element => <div className='DividerElement'/>)
|
||||
const DividerElement = (): JSX.Element => <div className='DividerElement'/>
|
||||
|
||||
contentRegistry.registerContentType({
|
||||
type: 'divider',
|
||||
@ -20,4 +20,4 @@ contentRegistry.registerContentType({
|
||||
createComponent: () => <DividerElement/>,
|
||||
})
|
||||
|
||||
export default DividerElement
|
||||
export default React.memo(DividerElement)
|
||||
|
@ -14,7 +14,7 @@ type Props = {
|
||||
block: ContentBlock
|
||||
}
|
||||
|
||||
const ImageElement = React.memo((props: Props): JSX.Element|null => {
|
||||
const ImageElement = (props: Props): JSX.Element|null => {
|
||||
const [imageDataUrl, setImageDataUrl] = useState<string|null>(null)
|
||||
|
||||
const {block} = props
|
||||
@ -40,7 +40,7 @@ const ImageElement = React.memo((props: Props): JSX.Element|null => {
|
||||
alt={block.title}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
contentRegistry.registerContentType({
|
||||
type: 'image',
|
||||
@ -65,4 +65,4 @@ contentRegistry.registerContentType({
|
||||
createComponent: (block) => <ImageElement block={block}/>,
|
||||
})
|
||||
|
||||
export default ImageElement
|
||||
export default React.memo(ImageElement)
|
||||
|
@ -16,7 +16,7 @@ type Props = {
|
||||
readonly: boolean
|
||||
}
|
||||
|
||||
const TextElement = React.memo((props: Props): JSX.Element => {
|
||||
const TextElement = (props: Props): JSX.Element => {
|
||||
const {block, readonly} = props
|
||||
const intl = useIntl()
|
||||
|
||||
@ -32,7 +32,7 @@ const TextElement = React.memo((props: Props): JSX.Element => {
|
||||
readonly={readonly}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
contentRegistry.registerContentType({
|
||||
type: 'text',
|
||||
@ -51,4 +51,4 @@ contentRegistry.registerContentType({
|
||||
},
|
||||
})
|
||||
|
||||
export default TextElement
|
||||
export default React.memo(TextElement)
|
||||
|
@ -34,7 +34,7 @@ type Props = {
|
||||
cords: {x: number, y?: number, z?: number}
|
||||
}
|
||||
|
||||
const ContentBlock = React.memo((props: Props): JSX.Element => {
|
||||
const ContentBlock = (props: Props): JSX.Element => {
|
||||
const {card, block, readonly, cords} = props
|
||||
const intl = useIntl()
|
||||
const [, , gripRef, itemRef] = useSortableWithGrip('content', {block, cords}, true, () => {})
|
||||
@ -158,6 +158,6 @@ const ContentBlock = React.memo((props: Props): JSX.Element => {
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default ContentBlock
|
||||
export default React.memo(ContentBlock)
|
||||
|
@ -16,12 +16,13 @@ type Props = {
|
||||
toolbar?: React.ReactNode
|
||||
hideCloseButton?: boolean
|
||||
className?: string
|
||||
title?: string
|
||||
onClose: () => void,
|
||||
}
|
||||
|
||||
const Dialog = React.memo((props: Props) => {
|
||||
const Dialog = (props: Props) => {
|
||||
const {toolsMenu} = props
|
||||
const {toolbar} = props
|
||||
const {toolbar, title} = props
|
||||
const intl = useIntl()
|
||||
|
||||
const closeDialogText = intl.formatMessage({
|
||||
@ -47,6 +48,7 @@ const Dialog = React.memo((props: Props) => {
|
||||
className='dialog'
|
||||
>
|
||||
<div className='toolbar'>
|
||||
{title && <h1 className='text-heading5 mt-2'>{title}</h1>}
|
||||
{
|
||||
!props.hideCloseButton &&
|
||||
<IconButton
|
||||
@ -71,6 +73,6 @@ const Dialog = React.memo((props: Props) => {
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default Dialog
|
||||
export default React.memo(Dialog)
|
||||
|
@ -42,7 +42,7 @@ type Props = {
|
||||
onDrop: (srcCard: Card, dstCard: Card) => void
|
||||
}
|
||||
|
||||
const GalleryCard = React.memo((props: Props) => {
|
||||
const GalleryCard = (props: Props) => {
|
||||
const {card, board} = props
|
||||
const intl = useIntl()
|
||||
const [isDragging, isOver, cardRef] = useSortable('card', card, props.isManualSort && !props.readonly, props.onDrop)
|
||||
@ -185,6 +185,6 @@ const GalleryCard = React.memo((props: Props) => {
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default GalleryCard
|
||||
export default React.memo(GalleryCard)
|
||||
|
@ -239,7 +239,7 @@ exports[`components/sidebar/GlobalHeaderSettingsMenu imports menu open should ma
|
||||
Random icons
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
class="Switch size--small on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -710,7 +710,7 @@ exports[`components/sidebar/GlobalHeaderSettingsMenu languages menu open should
|
||||
Random icons
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
class="Switch size--small on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -872,7 +872,7 @@ exports[`components/sidebar/GlobalHeaderSettingsMenu settings menu open should m
|
||||
Random icons
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
class="Switch size--small on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
|
@ -17,7 +17,7 @@ import TelemetryClient, {TelemetryCategory, TelemetryActions} from '../../teleme
|
||||
|
||||
import './globalHeaderSettingsMenu.scss'
|
||||
|
||||
const GlobalHeaderSettingsMenu = React.memo(() => {
|
||||
const GlobalHeaderSettingsMenu = () => {
|
||||
const intl = useIntl()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
@ -96,6 +96,6 @@ const GlobalHeaderSettingsMenu = React.memo(() => {
|
||||
</MenuWrapper>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default GlobalHeaderSettingsMenu
|
||||
export default React.memo(GlobalHeaderSettingsMenu)
|
||||
|
@ -85,9 +85,14 @@
|
||||
.octo-board-hidden-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
|
||||
> div {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
||||
.Label {
|
||||
margin: 5px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@ type Props = {
|
||||
isManualSort: boolean
|
||||
}
|
||||
|
||||
const KanbanCard = React.memo((props: Props) => {
|
||||
const KanbanCard = (props: Props) => {
|
||||
const {card, board} = props
|
||||
const intl = useIntl()
|
||||
const [isDragging, isOver, cardRef] = useSortable('card', card, !props.readonly, props.onDrop)
|
||||
@ -174,6 +174,6 @@ const KanbanCard = React.memo((props: Props) => {
|
||||
|
||||
</>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default KanbanCard
|
||||
export default React.memo(KanbanCard)
|
||||
|
@ -11,7 +11,7 @@ type Props = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const KanbanColumn = React.memo((props: Props) => {
|
||||
const KanbanColumn = (props: Props) => {
|
||||
const [{isOver}, drop] = useDrop(() => ({
|
||||
accept: 'card',
|
||||
collect: (monitor) => ({
|
||||
@ -36,6 +36,6 @@ const KanbanColumn = React.memo((props: Props) => {
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default KanbanColumn
|
||||
export default React.memo(KanbanColumn)
|
||||
|
@ -12,7 +12,7 @@ type Props = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const Modal = React.memo((props: Props): JSX.Element => {
|
||||
const Modal = (props: Props): JSX.Element => {
|
||||
const node = useRef<HTMLDivElement>(null)
|
||||
|
||||
const {position, onClose, children} = props
|
||||
@ -47,6 +47,6 @@ const Modal = React.memo((props: Props): JSX.Element => {
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default Modal
|
||||
export default React.memo(Modal)
|
||||
|
@ -7,12 +7,12 @@ type Props = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const ModalWrapper = React.memo((props: Props) => {
|
||||
const ModalWrapper = (props: Props) => {
|
||||
return (
|
||||
<div className='ModalWrapper'>
|
||||
{props.children}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default ModalWrapper
|
||||
export default React.memo(ModalWrapper)
|
||||
|
@ -20,7 +20,7 @@ type Props = {
|
||||
isEditable: boolean
|
||||
}
|
||||
|
||||
const SelectProperty = React.memo((props: Props) => {
|
||||
const SelectProperty = (props: Props) => {
|
||||
const {emptyValue, propertyValue, propertyTemplate, isEditable} = props
|
||||
const [open, setOpen] = useState(false)
|
||||
|
||||
@ -56,6 +56,6 @@ const SelectProperty = React.memo((props: Props) => {
|
||||
onBlur={() => setOpen(false)}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default SelectProperty
|
||||
export default React.memo(SelectProperty)
|
||||
|
@ -8,7 +8,7 @@ type Props = {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
const RootPortal = React.memo((props: Props): JSX.Element => {
|
||||
const RootPortal = (props: Props): JSX.Element => {
|
||||
const [el] = useState(document.createElement('div'))
|
||||
const rootPortal = document.getElementById('focalboard-root-portal')
|
||||
|
||||
@ -24,6 +24,6 @@ const RootPortal = React.memo((props: Props): JSX.Element => {
|
||||
}, [])
|
||||
|
||||
return ReactDOM.createPortal(props.children, el) // eslint-disable-line
|
||||
})
|
||||
}
|
||||
|
||||
export default RootPortal
|
||||
export default React.memo(RootPortal)
|
||||
|
@ -0,0 +1,950 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`src/components/shareBoard/shareBoard return shareBoard and click Copy link 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog"
|
||||
>
|
||||
<div
|
||||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="toolbar"
|
||||
>
|
||||
<h1
|
||||
class="text-heading5 mt-2"
|
||||
>
|
||||
Share Board
|
||||
</h1>
|
||||
<button
|
||||
aria-label="Close dialog"
|
||||
title="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="tabs-modal"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="d-flex justify-content-between"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<div
|
||||
class="text-heading2"
|
||||
>
|
||||
Publish to the web
|
||||
</div>
|
||||
<div
|
||||
class="text-light"
|
||||
>
|
||||
Publish and share a “read only” link with everyone on the web
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="Switch size--medium on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex justify-content-between tabs-inputs"
|
||||
>
|
||||
<div
|
||||
class="d-flex input-container"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/workspace/1/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/workspace/1/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<div
|
||||
class="octo-tooltip tooltip-top"
|
||||
data-tooltip="Regenerate token"
|
||||
>
|
||||
<button
|
||||
aria-label="Regenerate token"
|
||||
title="Regenerate token"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-refresh Icon Icon--right"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Copy link"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="CompassIcon icon-content-copy CompassIcon"
|
||||
/>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoard/shareBoard return shareBoard and click Copy link 2`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog"
|
||||
>
|
||||
<div
|
||||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="toolbar"
|
||||
>
|
||||
<h1
|
||||
class="text-heading5 mt-2"
|
||||
>
|
||||
Share Board
|
||||
</h1>
|
||||
<button
|
||||
aria-label="Close dialog"
|
||||
title="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="tabs-modal"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="d-flex justify-content-between"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<div
|
||||
class="text-heading2"
|
||||
>
|
||||
Publish to the web
|
||||
</div>
|
||||
<div
|
||||
class="text-light"
|
||||
>
|
||||
Publish and share a “read only” link with everyone on the web
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="Switch size--medium on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex justify-content-between tabs-inputs"
|
||||
>
|
||||
<div
|
||||
class="d-flex input-container"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/workspace/1/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/workspace/1/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<div
|
||||
class="octo-tooltip tooltip-top"
|
||||
data-tooltip="Regenerate token"
|
||||
>
|
||||
<button
|
||||
aria-label="Regenerate token"
|
||||
title="Regenerate token"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-refresh Icon Icon--right"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Copy link"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="CompassIcon icon-content-copy CompassIcon"
|
||||
/>
|
||||
Copied!
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoard/shareBoard return shareBoard and click Regenerate token 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog"
|
||||
>
|
||||
<div
|
||||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="toolbar"
|
||||
>
|
||||
<h1
|
||||
class="text-heading5 mt-2"
|
||||
>
|
||||
Share Board
|
||||
</h1>
|
||||
<button
|
||||
aria-label="Close dialog"
|
||||
title="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="tabs-modal"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="d-flex justify-content-between"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<div
|
||||
class="text-heading2"
|
||||
>
|
||||
Publish to the web
|
||||
</div>
|
||||
<div
|
||||
class="text-light"
|
||||
>
|
||||
Publish and share a “read only” link with everyone on the web
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="Switch size--medium on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex justify-content-between tabs-inputs"
|
||||
>
|
||||
<div
|
||||
class="d-flex input-container"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/workspace/1/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/workspace/1/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<div
|
||||
class="octo-tooltip tooltip-top"
|
||||
data-tooltip="Regenerate token"
|
||||
>
|
||||
<button
|
||||
aria-label="Regenerate token"
|
||||
title="Regenerate token"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-refresh Icon Icon--right"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Copy link"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="CompassIcon icon-content-copy CompassIcon"
|
||||
/>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoard/shareBoard return shareBoard, and click switch 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog"
|
||||
>
|
||||
<div
|
||||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="toolbar"
|
||||
>
|
||||
<h1
|
||||
class="text-heading5 mt-2"
|
||||
>
|
||||
Share Board
|
||||
</h1>
|
||||
<button
|
||||
aria-label="Close dialog"
|
||||
title="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="tabs-modal"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="d-flex justify-content-between"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<div
|
||||
class="text-heading2"
|
||||
>
|
||||
Publish to the web
|
||||
</div>
|
||||
<div
|
||||
class="text-light"
|
||||
>
|
||||
Publish and share a “read only” link with everyone on the web
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="Switch size--medium on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex justify-content-between tabs-inputs"
|
||||
>
|
||||
<div
|
||||
class="d-flex input-container"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/workspace/1/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/workspace/1/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<div
|
||||
class="octo-tooltip tooltip-top"
|
||||
data-tooltip="Regenerate token"
|
||||
>
|
||||
<button
|
||||
aria-label="Regenerate token"
|
||||
title="Regenerate token"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-refresh Icon Icon--right"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Copy link"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="CompassIcon icon-content-copy CompassIcon"
|
||||
/>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoard/shareBoard return shareBoardComponent and click Switch without sharing 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog"
|
||||
>
|
||||
<div
|
||||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="toolbar"
|
||||
>
|
||||
<h1
|
||||
class="text-heading5 mt-2"
|
||||
>
|
||||
Share Board
|
||||
</h1>
|
||||
<button
|
||||
aria-label="Close dialog"
|
||||
title="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="tabs-modal"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="d-flex justify-content-between"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<div
|
||||
class="text-heading2"
|
||||
>
|
||||
Publish to the web
|
||||
</div>
|
||||
<div
|
||||
class="text-light"
|
||||
>
|
||||
Publish and share a “read only” link with everyone on the web
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="Switch size--medium on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex justify-content-between tabs-inputs"
|
||||
>
|
||||
<div
|
||||
class="d-flex input-container"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/workspace/1/shared/1/1?r=aToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/workspace/1/shared/1/1?r=aToken
|
||||
</a>
|
||||
<div
|
||||
class="octo-tooltip tooltip-top"
|
||||
data-tooltip="Regenerate token"
|
||||
>
|
||||
<button
|
||||
aria-label="Regenerate token"
|
||||
title="Regenerate token"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-refresh Icon Icon--right"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Copy link"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="CompassIcon icon-content-copy CompassIcon"
|
||||
/>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoard/shareBoard should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog"
|
||||
>
|
||||
<div
|
||||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="toolbar"
|
||||
>
|
||||
<h1
|
||||
class="text-heading5 mt-2"
|
||||
>
|
||||
Share Board
|
||||
</h1>
|
||||
<button
|
||||
aria-label="Close dialog"
|
||||
title="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="tabs-modal"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="d-flex justify-content-between"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<div
|
||||
class="text-heading2"
|
||||
>
|
||||
Publish to the web
|
||||
</div>
|
||||
<div
|
||||
class="text-light"
|
||||
>
|
||||
Publish and share a “read only” link with everyone on the web
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="Switch size--medium"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoard/shareBoard should match snapshot with sharing 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog"
|
||||
>
|
||||
<div
|
||||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="toolbar"
|
||||
>
|
||||
<h1
|
||||
class="text-heading5 mt-2"
|
||||
>
|
||||
Share Board
|
||||
</h1>
|
||||
<button
|
||||
aria-label="Close dialog"
|
||||
title="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="tabs-modal"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="d-flex justify-content-between"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<div
|
||||
class="text-heading2"
|
||||
>
|
||||
Publish to the web
|
||||
</div>
|
||||
<div
|
||||
class="text-light"
|
||||
>
|
||||
Publish and share a “read only” link with everyone on the web
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="Switch size--medium on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex justify-content-between tabs-inputs"
|
||||
>
|
||||
<div
|
||||
class="d-flex input-container"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/workspace/1/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/workspace/1/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<div
|
||||
class="octo-tooltip tooltip-top"
|
||||
data-tooltip="Regenerate token"
|
||||
>
|
||||
<button
|
||||
aria-label="Regenerate token"
|
||||
title="Regenerate token"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-refresh Icon Icon--right"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Copy link"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="CompassIcon icon-content-copy CompassIcon"
|
||||
/>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoard/shareBoard should match snapshot with sharing and subpath 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog"
|
||||
>
|
||||
<div
|
||||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="toolbar"
|
||||
>
|
||||
<h1
|
||||
class="text-heading5 mt-2"
|
||||
>
|
||||
Share Board
|
||||
</h1>
|
||||
<button
|
||||
aria-label="Close dialog"
|
||||
title="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="tabs-modal"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="d-flex justify-content-between"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<div
|
||||
class="text-heading2"
|
||||
>
|
||||
Publish to the web
|
||||
</div>
|
||||
<div
|
||||
class="text-light"
|
||||
>
|
||||
Publish and share a “read only” link with everyone on the web
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="Switch size--medium on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex justify-content-between tabs-inputs"
|
||||
>
|
||||
<div
|
||||
class="d-flex input-container"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/test-subpath/plugins/boards/workspace/1/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/test-subpath/plugins/boards/workspace/1/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<div
|
||||
class="octo-tooltip tooltip-top"
|
||||
data-tooltip="Regenerate token"
|
||||
>
|
||||
<button
|
||||
aria-label="Regenerate token"
|
||||
title="Regenerate token"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-refresh Icon Icon--right"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Copy link"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="CompassIcon icon-content-copy CompassIcon"
|
||||
/>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`src/components/shareBoard/shareBoard should match snapshot with sharing and without workspaceId and subpath 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="Dialog dialog-back ShareBoardDialog"
|
||||
>
|
||||
<div
|
||||
class="wrapper"
|
||||
>
|
||||
<div
|
||||
class="dialog"
|
||||
role="dialog"
|
||||
>
|
||||
<div
|
||||
class="toolbar"
|
||||
>
|
||||
<h1
|
||||
class="text-heading5 mt-2"
|
||||
>
|
||||
Share Board
|
||||
</h1>
|
||||
<button
|
||||
aria-label="Close dialog"
|
||||
title="Close dialog"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-close CloseIcon"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
class="tabs-modal"
|
||||
>
|
||||
<div>
|
||||
<div
|
||||
class="d-flex justify-content-between"
|
||||
>
|
||||
<div
|
||||
class="d-flex flex-column"
|
||||
>
|
||||
<div
|
||||
class="text-heading2"
|
||||
>
|
||||
Publish to the web
|
||||
</div>
|
||||
<div
|
||||
class="text-light"
|
||||
>
|
||||
Publish and share a “read only” link with everyone on the web
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
class="Switch size--medium on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="d-flex justify-content-between tabs-inputs"
|
||||
>
|
||||
<div
|
||||
class="d-flex input-container"
|
||||
>
|
||||
<a
|
||||
class="shareUrl"
|
||||
href="http://localhost/test-subpath/plugins/boards/shared/1/1?r=oneToken"
|
||||
rel="noreferrer"
|
||||
target="_blank"
|
||||
>
|
||||
http://localhost/test-subpath/plugins/boards/shared/1/1?r=oneToken
|
||||
</a>
|
||||
<div
|
||||
class="octo-tooltip tooltip-top"
|
||||
data-tooltip="Regenerate token"
|
||||
>
|
||||
<button
|
||||
aria-label="Regenerate token"
|
||||
title="Regenerate token"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-refresh Icon Icon--right"
|
||||
/>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
title="Copy link"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="CompassIcon icon-content-copy CompassIcon"
|
||||
/>
|
||||
Copy link
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
@ -0,0 +1,22 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`src/components/shareBoard/shareBoard should match snapshot 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ShareBoardButton"
|
||||
>
|
||||
<button
|
||||
class="Button emphasis--secondary size--medium"
|
||||
title="Share board"
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
<i
|
||||
class="CompassIcon icon-globe CompassIcon"
|
||||
/>
|
||||
Share
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
66
webapp/src/components/shareBoard/shareBoard.scss
Normal file
66
webapp/src/components/shareBoard/shareBoard.scss
Normal file
@ -0,0 +1,66 @@
|
||||
.ShareBoardDialog {
|
||||
.dialog {
|
||||
@media not screen and (max-width: 975px) {
|
||||
max-width: 600px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.tabs-modal {
|
||||
padding: 24px;
|
||||
border-radius: 8px;
|
||||
background: rgba(var(--center-channel-bg-rgb), 1);
|
||||
border-top: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
||||
|
||||
.d-flex {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.justify-content-between {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.flex-column {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tabs-inputs {
|
||||
margin-top: 16px;
|
||||
|
||||
.input-container {
|
||||
border: 1px solid rgba(var(--center-channel-color-rgb), 0.16);
|
||||
border-radius: 4px;
|
||||
width: 400px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.IconButton {
|
||||
height: 100%;
|
||||
width: 40px;
|
||||
}
|
||||
|
||||
.icon-refresh {
|
||||
margin: 4px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.icon-content-copy {
|
||||
margin-right: 4px;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.shareUrl {
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: auto 4px;
|
||||
padding: 0 16px;
|
||||
|
||||
:hover {
|
||||
background-color: rgb(var(--center-channel-color-rgb), 0.1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,13 +7,13 @@ import React from 'react'
|
||||
import {MemoryRouter} from 'react-router'
|
||||
import {mocked} from 'ts-jest/utils'
|
||||
|
||||
import {ISharing} from '../blocks/sharing'
|
||||
import {TestBlockFactory} from '../test/testBlockFactory'
|
||||
import {wrapDNDIntl} from '../testUtils'
|
||||
import client from '../octoClient'
|
||||
import {Utils} from '../utils'
|
||||
import {ISharing} from '../../blocks/sharing'
|
||||
import {TestBlockFactory} from '../../test/testBlockFactory'
|
||||
import {wrapDNDIntl} from '../../testUtils'
|
||||
import client from '../../octoClient'
|
||||
import {Utils} from '../../utils'
|
||||
|
||||
import ShareBoardComponent from './shareBoardComponent'
|
||||
import ShareBoard from './shareBoard'
|
||||
|
||||
jest.useFakeTimers()
|
||||
|
||||
@ -21,8 +21,8 @@ const boardId = '1'
|
||||
const workspaceId: string|undefined = boardId
|
||||
const viewId = boardId
|
||||
|
||||
jest.mock('../octoClient')
|
||||
jest.mock('../utils')
|
||||
jest.mock('../../octoClient')
|
||||
jest.mock('../../utils')
|
||||
|
||||
const mockedOctoClient = mocked(client, true)
|
||||
const mockedUtils = mocked(Utils, true)
|
||||
@ -46,7 +46,7 @@ jest.mock('react-router', () => {
|
||||
const board = TestBlockFactory.createBoard()
|
||||
board.id = boardId
|
||||
|
||||
describe('src/components/shareBoardComponent', () => {
|
||||
describe('src/components/shareBoard/shareBoard', () => {
|
||||
const w = (window as any)
|
||||
const oldBaseURL = w.baseURL
|
||||
|
||||
@ -69,15 +69,22 @@ describe('src/components/shareBoardComponent', () => {
|
||||
mockedOctoClient.getSharing.mockResolvedValue(undefined)
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ShareBoardComponent
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>), {wrapper: MemoryRouter})
|
||||
const result = render(
|
||||
wrapDNDIntl(
|
||||
<ShareBoard
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>),
|
||||
{wrapper: MemoryRouter},
|
||||
)
|
||||
container = result.container
|
||||
})
|
||||
|
||||
expect(container).toMatchSnapshot()
|
||||
const closeButton = screen.getByRole('button', {name: 'Close dialog'})
|
||||
expect(closeButton).toBeDefined()
|
||||
})
|
||||
|
||||
test('should match snapshot with sharing', async () => {
|
||||
const sharing:ISharing = {
|
||||
id: boardId,
|
||||
@ -85,62 +92,64 @@ describe('src/components/shareBoardComponent', () => {
|
||||
token: 'oneToken',
|
||||
}
|
||||
mockedOctoClient.getSharing.mockResolvedValue(sharing)
|
||||
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ShareBoardComponent
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>), {wrapper: MemoryRouter})
|
||||
container = result.container
|
||||
})
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('should match snapshot with sharing and without workspaceId', async () => {
|
||||
const sharing:ISharing = {
|
||||
id: boardId,
|
||||
enabled: true,
|
||||
token: 'oneToken',
|
||||
}
|
||||
params = {
|
||||
boardId,
|
||||
viewId,
|
||||
}
|
||||
mockedOctoClient.getSharing.mockResolvedValue(sharing)
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ShareBoardComponent
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>), {wrapper: MemoryRouter})
|
||||
container = result.container
|
||||
})
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('return shareBoardComponent and click Copy link', async () => {
|
||||
const sharing:ISharing = {
|
||||
id: boardId,
|
||||
enabled: true,
|
||||
token: 'oneToken',
|
||||
}
|
||||
mockedOctoClient.getSharing.mockResolvedValue(sharing)
|
||||
let container: Element | undefined
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ShareBoardComponent
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>), {wrapper: MemoryRouter})
|
||||
const result = render(
|
||||
wrapDNDIntl(
|
||||
<ShareBoard
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>),
|
||||
{wrapper: MemoryRouter},
|
||||
)
|
||||
container = result.container
|
||||
})
|
||||
const copyLinkElement = screen.getByRole('button', {name: 'Copy link'})
|
||||
expect(copyLinkElement).toBeDefined()
|
||||
userEvent.click(copyLinkElement)
|
||||
expect(mockedUtils.copyTextToClipboard).toBeCalledTimes(1)
|
||||
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('return shareBoardComponent and click Regenerate token', async () => {
|
||||
|
||||
test('return shareBoard and click Copy link', async () => {
|
||||
const sharing:ISharing = {
|
||||
id: boardId,
|
||||
enabled: true,
|
||||
token: 'oneToken',
|
||||
}
|
||||
mockedOctoClient.getSharing.mockResolvedValue(sharing)
|
||||
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(
|
||||
wrapDNDIntl(
|
||||
<ShareBoard
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>),
|
||||
{wrapper: MemoryRouter},
|
||||
)
|
||||
container = result.container
|
||||
})
|
||||
|
||||
expect(container).toMatchSnapshot()
|
||||
|
||||
const copyLinkElement = screen.getByRole('button', {name: 'Copy link'})
|
||||
expect(copyLinkElement).toBeDefined()
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(copyLinkElement!)
|
||||
})
|
||||
|
||||
expect(mockedUtils.copyTextToClipboard).toBeCalledTimes(1)
|
||||
expect(container).toMatchSnapshot()
|
||||
|
||||
const copiedLinkElement = screen.getByRole('button', {name: 'Copy link'})
|
||||
expect(copiedLinkElement).toBeDefined()
|
||||
expect(copiedLinkElement.textContent).toContain('Copied!')
|
||||
})
|
||||
|
||||
test('return shareBoard and click Regenerate token', async () => {
|
||||
window.confirm = jest.fn(() => {
|
||||
return true
|
||||
})
|
||||
@ -150,15 +159,20 @@ describe('src/components/shareBoardComponent', () => {
|
||||
token: 'oneToken',
|
||||
}
|
||||
mockedOctoClient.getSharing.mockResolvedValue(sharing)
|
||||
let container: Element | undefined
|
||||
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ShareBoardComponent
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>), {wrapper: MemoryRouter})
|
||||
const result = render(
|
||||
wrapDNDIntl(
|
||||
<ShareBoard
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>),
|
||||
{wrapper: MemoryRouter},
|
||||
)
|
||||
container = result.container
|
||||
})
|
||||
|
||||
sharing.token = 'anotherToken'
|
||||
mockedUtils.createGuid.mockReturnValue('anotherToken')
|
||||
mockedOctoClient.getSharing.mockResolvedValue(sharing)
|
||||
@ -172,7 +186,7 @@ describe('src/components/shareBoardComponent', () => {
|
||||
expect(mockedOctoClient.setSharing).toBeCalledTimes(1)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('return shareBoardComponent and click Switch', async () => {
|
||||
test('return shareBoard, and click switch', async () => {
|
||||
const sharing:ISharing = {
|
||||
id: boardId,
|
||||
enabled: true,
|
||||
@ -181,18 +195,24 @@ describe('src/components/shareBoardComponent', () => {
|
||||
mockedOctoClient.getSharing.mockResolvedValue(sharing)
|
||||
let container: Element | undefined
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ShareBoardComponent
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>), {wrapper: MemoryRouter})
|
||||
const result = render(
|
||||
wrapDNDIntl(
|
||||
<ShareBoard
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>),
|
||||
{wrapper: MemoryRouter},
|
||||
)
|
||||
container = result.container
|
||||
})
|
||||
const switchElement = container?.querySelector('.Switch')
|
||||
expect(switchElement).toBeDefined()
|
||||
userEvent.click(switchElement!)
|
||||
await act(async () => {
|
||||
userEvent.click(switchElement!)
|
||||
})
|
||||
|
||||
expect(mockedOctoClient.setSharing).toBeCalledTimes(1)
|
||||
expect(mockedOctoClient.getSharing).toBeCalledTimes(1)
|
||||
expect(mockedOctoClient.getSharing).toBeCalledTimes(2)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
test('return shareBoardComponent and click Switch without sharing', async () => {
|
||||
@ -200,11 +220,14 @@ describe('src/components/shareBoardComponent', () => {
|
||||
mockedUtils.createGuid.mockReturnValue('aToken')
|
||||
let container: Element | undefined
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ShareBoardComponent
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>), {wrapper: MemoryRouter})
|
||||
const result = render(
|
||||
wrapDNDIntl(
|
||||
<ShareBoard
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>),
|
||||
{wrapper: MemoryRouter},
|
||||
)
|
||||
container = result.container
|
||||
mockedOctoClient.getSharing.mockResolvedValue({
|
||||
id: boardId,
|
||||
@ -215,11 +238,12 @@ describe('src/components/shareBoardComponent', () => {
|
||||
expect(switchElement).toBeDefined()
|
||||
userEvent.click(switchElement!)
|
||||
jest.runOnlyPendingTimers()
|
||||
result.rerender(wrapDNDIntl(
|
||||
<ShareBoardComponent
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>))
|
||||
result.rerender(
|
||||
wrapDNDIntl(
|
||||
<ShareBoard
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>))
|
||||
})
|
||||
|
||||
expect(mockedOctoClient.setSharing).toBeCalledTimes(1)
|
||||
@ -227,7 +251,6 @@ describe('src/components/shareBoardComponent', () => {
|
||||
expect(mockedUtils.createGuid).toBeCalledTimes(1)
|
||||
expect(container).toMatchSnapshot()
|
||||
})
|
||||
|
||||
test('should match snapshot with sharing and without workspaceId and subpath', async () => {
|
||||
w.baseURL = '/test-subpath/plugins/boards'
|
||||
const sharing:ISharing = {
|
||||
@ -243,7 +266,7 @@ describe('src/components/shareBoardComponent', () => {
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ShareBoardComponent
|
||||
<ShareBoard
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>), {wrapper: MemoryRouter})
|
||||
@ -263,7 +286,7 @@ describe('src/components/shareBoardComponent', () => {
|
||||
let container
|
||||
await act(async () => {
|
||||
const result = render(wrapDNDIntl(
|
||||
<ShareBoardComponent
|
||||
<ShareBoard
|
||||
boardId={board.id}
|
||||
onClose={jest.fn()}
|
||||
/>), {wrapper: MemoryRouter})
|
178
webapp/src/components/shareBoard/shareBoard.tsx
Normal file
178
webapp/src/components/shareBoard/shareBoard.tsx
Normal file
@ -0,0 +1,178 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {useIntl, FormattedMessage} from 'react-intl'
|
||||
import {generatePath, useRouteMatch} from 'react-router'
|
||||
|
||||
import {Utils, IDType} from '../../utils'
|
||||
import Tooltip from '../../widgets/tooltip'
|
||||
|
||||
import {ISharing} from '../../blocks/sharing'
|
||||
|
||||
import client from '../../octoClient'
|
||||
import Dialog from '../dialog'
|
||||
import Switch from '../../widgets/switch'
|
||||
import Button from '../../widgets/buttons/button'
|
||||
import {sendFlashMessage} from '../flashMessages'
|
||||
|
||||
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'
|
||||
|
||||
import CompassIcon from '../../widgets/icons/compassIcon'
|
||||
import IconButton from '../../widgets/buttons/iconButton'
|
||||
import './shareBoard.scss'
|
||||
|
||||
type Props = {
|
||||
boardId: string
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
export default function ShareBoardDialog(props: Props): JSX.Element {
|
||||
const [wasCopied, setWasCopied] = useState(false)
|
||||
const [sharing, setSharing] = useState<ISharing|undefined>(undefined)
|
||||
|
||||
const intl = useIntl()
|
||||
const match = useRouteMatch<{workspaceId?: string, boardId: string, viewId: string}>()
|
||||
|
||||
const loadData = async () => {
|
||||
const newSharing = await client.getSharing(props.boardId)
|
||||
setSharing(newSharing)
|
||||
setWasCopied(false)
|
||||
}
|
||||
|
||||
const createSharingInfo = () => {
|
||||
const newSharing: ISharing = {
|
||||
id: props.boardId,
|
||||
enabled: true,
|
||||
token: Utils.createGuid(IDType.Token),
|
||||
}
|
||||
return newSharing
|
||||
}
|
||||
|
||||
const onShareChanged = async (isOn: boolean) => {
|
||||
const newSharing: ISharing = sharing || createSharingInfo()
|
||||
newSharing.id = props.boardId
|
||||
newSharing.enabled = isOn
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareBoard, {board: props.boardId, shareBoardEnabled: isOn})
|
||||
await client.setSharing(newSharing)
|
||||
await loadData()
|
||||
}
|
||||
|
||||
const onRegenerateToken = async () => {
|
||||
// eslint-disable-next-line no-alert
|
||||
const accept = window.confirm(intl.formatMessage({id: 'ShareBoard.confirmRegenerateToken', defaultMessage: 'This will invalidate previously shared links. Continue?'}))
|
||||
if (accept) {
|
||||
const newSharing: ISharing = sharing || createSharingInfo()
|
||||
newSharing.token = Utils.createGuid(IDType.Token)
|
||||
await client.setSharing(newSharing)
|
||||
await loadData()
|
||||
|
||||
const description = intl.formatMessage({id: 'ShareBoard.tokenRegenrated', defaultMessage: 'Token regenerated'})
|
||||
sendFlashMessage({content: description, severity: 'low'})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadData()
|
||||
}, [])
|
||||
|
||||
const isSharing = Boolean(sharing && sharing.id === props.boardId && sharing.enabled)
|
||||
const readToken = (sharing && isSharing) ? sharing.token : ''
|
||||
const shareUrl = new URL(window.location.toString())
|
||||
shareUrl.searchParams.set('r', readToken)
|
||||
|
||||
if (match.params.workspaceId) {
|
||||
const newPath = generatePath('/workspace/:workspaceId/shared/:boardId/:viewId', {
|
||||
boardId: match.params.boardId,
|
||||
viewId: match.params.viewId,
|
||||
workspaceId: match.params.workspaceId,
|
||||
})
|
||||
shareUrl.pathname = Utils.buildURL(newPath)
|
||||
} else {
|
||||
const newPath = generatePath('/shared/:boardId/:viewId', {
|
||||
boardId: match.params.boardId,
|
||||
viewId: match.params.viewId,
|
||||
})
|
||||
shareUrl.pathname = Utils.buildURL(newPath)
|
||||
}
|
||||
|
||||
return (
|
||||
<Dialog
|
||||
onClose={props.onClose}
|
||||
className='ShareBoardDialog'
|
||||
title={intl.formatMessage({id: 'ShareBoard.Title', defaultMessage: 'Share Board'})}
|
||||
>
|
||||
<div className='tabs-modal'>
|
||||
<div>
|
||||
<div className='d-flex justify-content-between'>
|
||||
<div className='d-flex flex-column'>
|
||||
<div className='text-heading2'>{intl.formatMessage({id: 'ShareBoard.PublishTitle', defaultMessage: 'Publish to the web'})}</div>
|
||||
<div className='text-light'>{intl.formatMessage({id: 'ShareBoard.PublishDescription', defaultMessage: 'Publish and share a “read only” link with everyone on the web'})}</div>
|
||||
</div>
|
||||
<div>
|
||||
<Switch
|
||||
isOn={isSharing}
|
||||
size='medium'
|
||||
onChanged={onShareChanged}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{isSharing &&
|
||||
(<div className='d-flex justify-content-between tabs-inputs'>
|
||||
<div className='d-flex input-container'>
|
||||
<a
|
||||
className='shareUrl'
|
||||
href={shareUrl.toString()}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
{shareUrl.toString()}
|
||||
</a>
|
||||
<Tooltip
|
||||
key={'regenerateToken'}
|
||||
title={intl.formatMessage({id: 'ShareBoard.regenerate', defaultMessage: 'Regenerate token'})}
|
||||
>
|
||||
<IconButton
|
||||
onClick={onRegenerateToken}
|
||||
icon={
|
||||
<CompassIcon
|
||||
icon='refresh'
|
||||
className='Icon Icon--right'
|
||||
/>}
|
||||
title={intl.formatMessage({id: 'ShareBoard.regenerate', defaultMessage: 'Regenerate token'})}
|
||||
className='IconButton--large'
|
||||
/>
|
||||
</Tooltip>
|
||||
</div>
|
||||
<Button
|
||||
emphasis='secondary'
|
||||
size='medium'
|
||||
title='Copy link'
|
||||
onClick={() => {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareLinkPublicCopy, {board: props.boardId})
|
||||
Utils.copyTextToClipboard(shareUrl.toString())
|
||||
setWasCopied(true)
|
||||
}}
|
||||
>
|
||||
<CompassIcon
|
||||
icon='content-copy'
|
||||
className='CompassIcon'
|
||||
/>
|
||||
{wasCopied &&
|
||||
<FormattedMessage
|
||||
id='ShareBoard.copiedLink'
|
||||
defaultMessage='Copied!'
|
||||
/>}
|
||||
{!wasCopied &&
|
||||
<FormattedMessage
|
||||
id='ShareBoard.copyLink'
|
||||
defaultMessage='Copy link'
|
||||
/>}
|
||||
</Button>
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
</Dialog>
|
||||
)
|
||||
}
|
20
webapp/src/components/shareBoard/shareBoardButton.scss
Normal file
20
webapp/src/components/shareBoard/shareBoardButton.scss
Normal file
@ -0,0 +1,20 @@
|
||||
.ShareBoardButton {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 8px;
|
||||
|
||||
> .Button {
|
||||
margin-top: 36px;
|
||||
padding: 3px 10px;
|
||||
background-color: rgb(var(--button-bg-rgb));
|
||||
color: rgb(var(--button-color-rgb));
|
||||
border-radius: 5px;
|
||||
|
||||
&:hover {
|
||||
background-color: rgba(var(--button-bg-rgb), 0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
webapp/src/components/shareBoard/shareBoardButton.test.tsx
Normal file
30
webapp/src/components/shareBoard/shareBoardButton.test.tsx
Normal file
@ -0,0 +1,30 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import {render} from '@testing-library/react'
|
||||
import React from 'react'
|
||||
|
||||
import {TestBlockFactory} from '../../test/testBlockFactory'
|
||||
import {wrapDNDIntl} from '../../testUtils'
|
||||
|
||||
import ShareBoardButton from './shareBoardButton'
|
||||
|
||||
jest.useFakeTimers()
|
||||
|
||||
const boardId = '1'
|
||||
|
||||
const board = TestBlockFactory.createBoard()
|
||||
board.id = boardId
|
||||
|
||||
describe('src/components/shareBoard/shareBoard', () => {
|
||||
test('should match snapshot', async () => {
|
||||
const result = render(
|
||||
wrapDNDIntl(
|
||||
<ShareBoardButton
|
||||
boardId={board.id}
|
||||
/>))
|
||||
|
||||
const renderer = result.container
|
||||
|
||||
expect(renderer).toMatchSnapshot()
|
||||
})
|
||||
})
|
52
webapp/src/components/shareBoard/shareBoardButton.tsx
Normal file
52
webapp/src/components/shareBoard/shareBoardButton.tsx
Normal file
@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState} from 'react'
|
||||
import {FormattedMessage} from 'react-intl'
|
||||
|
||||
import Button from '../../widgets/buttons/button'
|
||||
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../../telemetry/telemetryClient'
|
||||
|
||||
import CompassIcon from '../../widgets/icons/compassIcon'
|
||||
|
||||
import './shareBoardButton.scss'
|
||||
|
||||
import ShareBoardDialog from './shareBoard'
|
||||
|
||||
type Props = {
|
||||
boardId: string
|
||||
}
|
||||
const ShareBoardButton = (props: Props) => {
|
||||
const [showShareDialog, setShowShareDialog] = useState(false)
|
||||
|
||||
return (
|
||||
<div className='ShareBoardButton'>
|
||||
<Button
|
||||
title='Share board'
|
||||
size='medium'
|
||||
emphasis='secondary'
|
||||
onClick={() => {
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareBoardOpenModal, {board: props.boardId})
|
||||
setShowShareDialog(!showShareDialog)
|
||||
}}
|
||||
>
|
||||
<CompassIcon
|
||||
icon='globe'
|
||||
className='CompassIcon'
|
||||
/>
|
||||
<FormattedMessage
|
||||
id='CenterPanel.Share'
|
||||
defaultMessage='Share'
|
||||
/>
|
||||
</Button>
|
||||
{showShareDialog &&
|
||||
<ShareBoardDialog
|
||||
onClose={() => setShowShareDialog(false)}
|
||||
boardId={props.boardId}
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default React.memo(ShareBoardButton)
|
@ -1,36 +0,0 @@
|
||||
.ShareBoardComponent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 5px;
|
||||
color: rgb(var(--center-channel-color-rgb));
|
||||
max-width: 500px;
|
||||
white-space: normal;
|
||||
|
||||
.Switch {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
> .row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
margin: 0 0 10px;
|
||||
}
|
||||
|
||||
> .row:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
a.shareUrl {
|
||||
max-width: 320px;
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-right: 5px;
|
||||
}
|
||||
}
|
@ -1,167 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
import React, {useState, useEffect} from 'react'
|
||||
import {FormattedMessage, useIntl} from 'react-intl'
|
||||
import {generatePath, useRouteMatch} from 'react-router'
|
||||
|
||||
import {ISharing} from '../blocks/sharing'
|
||||
|
||||
import client from '../octoClient'
|
||||
|
||||
import {Utils, IDType} from '../utils'
|
||||
import {sendFlashMessage} from '../components/flashMessages'
|
||||
|
||||
import Button from '../widgets/buttons/button'
|
||||
import Switch from '../widgets/switch'
|
||||
|
||||
import TelemetryClient, {TelemetryActions, TelemetryCategory} from '../telemetry/telemetryClient'
|
||||
|
||||
import Modal from './modal'
|
||||
import './shareBoardComponent.scss'
|
||||
|
||||
type Props = {
|
||||
boardId: string
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const ShareBoardComponent = React.memo((props: Props): JSX.Element => {
|
||||
const [wasCopied, setWasCopied] = useState(false)
|
||||
const [sharing, setSharing] = useState<ISharing|undefined>(undefined)
|
||||
const intl = useIntl()
|
||||
const match = useRouteMatch<{workspaceId?: string, boardId: string, viewId: string}>()
|
||||
|
||||
const loadData = async () => {
|
||||
const newSharing = await client.getSharing(props.boardId)
|
||||
setSharing(newSharing)
|
||||
setWasCopied(false)
|
||||
}
|
||||
|
||||
const createSharingInfo = () => {
|
||||
const newSharing: ISharing = {
|
||||
id: props.boardId,
|
||||
enabled: true,
|
||||
token: Utils.createGuid(IDType.Token),
|
||||
}
|
||||
return newSharing
|
||||
}
|
||||
|
||||
const onShareChanged = async (isOn: boolean) => {
|
||||
const newSharing: ISharing = sharing || createSharingInfo()
|
||||
newSharing.id = props.boardId
|
||||
newSharing.enabled = isOn
|
||||
TelemetryClient.trackEvent(TelemetryCategory, TelemetryActions.ShareBoard, {board: props.boardId, shareBoardEnabled: isOn})
|
||||
await client.setSharing(newSharing)
|
||||
await loadData()
|
||||
}
|
||||
|
||||
const onRegenerateToken = async () => {
|
||||
// eslint-disable-next-line no-alert
|
||||
const accept = window.confirm(intl.formatMessage({id: 'ShareBoard.confirmRegenerateToken', defaultMessage: 'This will invalidate previously shared links. Continue?'}))
|
||||
if (accept) {
|
||||
const newSharing: ISharing = sharing || createSharingInfo()
|
||||
newSharing.token = Utils.createGuid(IDType.Token)
|
||||
await client.setSharing(newSharing)
|
||||
await loadData()
|
||||
|
||||
const description = intl.formatMessage({id: 'ShareBoard.tokenRegenrated', defaultMessage: 'Token regenerated'})
|
||||
sendFlashMessage({content: description, severity: 'low'})
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
loadData()
|
||||
}, [])
|
||||
|
||||
const isSharing = Boolean(sharing && sharing.id === props.boardId && sharing.enabled)
|
||||
const readToken = (sharing && isSharing) ? sharing.token : ''
|
||||
|
||||
const shareUrl = new URL(window.location.toString())
|
||||
shareUrl.searchParams.set('r', readToken)
|
||||
|
||||
if (match.params.workspaceId) {
|
||||
const newPath = generatePath('/workspace/:workspaceId/shared/:boardId/:viewId', {
|
||||
boardId: match.params.boardId,
|
||||
viewId: match.params.viewId,
|
||||
workspaceId: match.params.workspaceId,
|
||||
})
|
||||
shareUrl.pathname = Utils.buildURL(newPath)
|
||||
} else {
|
||||
const newPath = generatePath('/shared/:boardId/:viewId', {
|
||||
boardId: match.params.boardId,
|
||||
viewId: match.params.viewId,
|
||||
})
|
||||
shareUrl.pathname = Utils.buildURL(newPath)
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal
|
||||
onClose={props.onClose}
|
||||
>
|
||||
<div className='ShareBoardComponent'>
|
||||
<div className='row'>
|
||||
<div>
|
||||
{isSharing &&
|
||||
<FormattedMessage
|
||||
id='ShareBoard.unshare'
|
||||
defaultMessage='Anyone with the link can view this board and all cards in it.'
|
||||
/>}
|
||||
{!isSharing &&
|
||||
<FormattedMessage
|
||||
id='ShareBoard.share'
|
||||
defaultMessage='Publish and share this board with anyone who has the link'
|
||||
/>}
|
||||
</div>
|
||||
<Switch
|
||||
isOn={Boolean(isSharing)}
|
||||
onChanged={onShareChanged}
|
||||
/>
|
||||
</div>
|
||||
{isSharing && <>
|
||||
<div className='row'>
|
||||
<a
|
||||
className='shareUrl'
|
||||
href={shareUrl.toString()}
|
||||
target='_blank'
|
||||
rel='noreferrer'
|
||||
>
|
||||
{shareUrl.toString()}
|
||||
</a>
|
||||
<Button
|
||||
filled={true}
|
||||
size='small'
|
||||
onClick={() => {
|
||||
Utils.copyTextToClipboard(shareUrl.toString())
|
||||
setWasCopied(true)
|
||||
}}
|
||||
>
|
||||
{wasCopied &&
|
||||
<FormattedMessage
|
||||
id='ShareBoard.copiedLink'
|
||||
defaultMessage='Copied!'
|
||||
/>}
|
||||
{!wasCopied &&
|
||||
<FormattedMessage
|
||||
id='ShareBoard.copyLink'
|
||||
defaultMessage='Copy link'
|
||||
/>}
|
||||
</Button>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<Button
|
||||
onClick={onRegenerateToken}
|
||||
emphasis='secondary'
|
||||
size='small'
|
||||
>
|
||||
<FormattedMessage
|
||||
id='ShareBoard.regenerateToken'
|
||||
defaultMessage='Regenerate token'
|
||||
/>
|
||||
</Button>
|
||||
</div>
|
||||
</>}
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
})
|
||||
|
||||
export default ShareBoardComponent
|
@ -259,7 +259,7 @@ exports[`components/sidebar/SidebarSettingsMenu imports menu open should match s
|
||||
Random icons
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
class="Switch size--small on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -750,7 +750,7 @@ exports[`components/sidebar/SidebarSettingsMenu languages menu open should match
|
||||
Random icons
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
class="Switch size--small on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -930,7 +930,7 @@ exports[`components/sidebar/SidebarSettingsMenu settings menu open should match
|
||||
Random icons
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
class="Switch size--small on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -1200,7 +1200,7 @@ exports[`components/sidebar/SidebarSettingsMenu theme menu open should match sna
|
||||
Random icons
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
class="Switch size--small on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
|
@ -19,7 +19,7 @@ type Props = {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const RegistrationLink = React.memo((props: Props) => {
|
||||
const RegistrationLink = (props: Props) => {
|
||||
const {onClose} = props
|
||||
const intl = useIntl()
|
||||
const workspace = useAppSelector<IWorkspace|null>(getCurrentWorkspace)
|
||||
@ -89,6 +89,6 @@ const RegistrationLink = React.memo((props: Props) => {
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default RegistrationLink
|
||||
export default React.memo(RegistrationLink)
|
||||
|
@ -39,7 +39,7 @@ function getWindowDimensions() {
|
||||
}
|
||||
}
|
||||
|
||||
const Sidebar = React.memo((props: Props) => {
|
||||
const Sidebar = (props: Props) => {
|
||||
const [isHidden, setHidden] = useState(false)
|
||||
const [userHidden, setUserHidden] = useState(false)
|
||||
const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions())
|
||||
@ -209,6 +209,6 @@ const Sidebar = React.memo((props: Props) => {
|
||||
<SidebarSettingsMenu activeTheme={getActiveThemeName()}/>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default Sidebar
|
||||
export default React.memo(Sidebar)
|
||||
|
@ -33,7 +33,7 @@ type Props = {
|
||||
hideSidebar: () => void
|
||||
}
|
||||
|
||||
const SidebarBoardItem = React.memo((props: Props) => {
|
||||
const SidebarBoardItem = (props: Props) => {
|
||||
const [collapsed, setCollapsed] = useState(false)
|
||||
const intl = useIntl()
|
||||
const history = useHistory()
|
||||
@ -207,6 +207,6 @@ const SidebarBoardItem = React.memo((props: Props) => {
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default SidebarBoardItem
|
||||
export default React.memo(SidebarBoardItem)
|
||||
|
@ -30,7 +30,7 @@ type Props = {
|
||||
activeTheme: string
|
||||
}
|
||||
|
||||
const SidebarSettingsMenu = React.memo((props: Props) => {
|
||||
const SidebarSettingsMenu = (props: Props) => {
|
||||
const intl = useIntl()
|
||||
const dispatch = useAppDispatch()
|
||||
|
||||
@ -164,6 +164,6 @@ const SidebarSettingsMenu = React.memo((props: Props) => {
|
||||
</MenuWrapper>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default SidebarSettingsMenu
|
||||
export default React.memo(SidebarSettingsMenu)
|
||||
|
@ -25,7 +25,7 @@ import './sidebarUserMenu.scss'
|
||||
|
||||
declare let window: IAppWindow
|
||||
|
||||
const SidebarUserMenu = React.memo(() => {
|
||||
const SidebarUserMenu = () => {
|
||||
const history = useHistory()
|
||||
const [showRegistrationLinkDialog, setShowRegistrationLinkDialog] = useState(false)
|
||||
const user = useAppSelector<IUser|null>(getMe)
|
||||
@ -106,6 +106,6 @@ const SidebarUserMenu = React.memo(() => {
|
||||
</ModalWrapper>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default SidebarUserMenu
|
||||
export default React.memo(SidebarUserMenu)
|
||||
|
@ -39,7 +39,7 @@ const CalculationRow = (props: Props): JSX.Element => {
|
||||
|
||||
const templates: IPropertyTemplate[] = [
|
||||
titleTemplate,
|
||||
...props.board.fields.cardProperties.filter((template) => props.activeView.fields.visiblePropertyIds.includes(template.id)),
|
||||
...props.activeView.fields.visiblePropertyIds.map((id) => props.board.fields.cardProperties.find((t) => t.id === id)).filter((i) => i) as IPropertyTemplate[],
|
||||
]
|
||||
|
||||
const selectedCalculations = props.board.fields.columnCalculations || []
|
||||
|
@ -10,7 +10,7 @@ type Props = {
|
||||
onAutoSizeColumn: (columnID: string) => void;
|
||||
}
|
||||
|
||||
const HorizontalGrip = React.memo((props: Props): JSX.Element => {
|
||||
const HorizontalGrip = (props: Props): JSX.Element => {
|
||||
const [, drag] = useDrag(() => ({
|
||||
type: 'horizontalGrip',
|
||||
item: {id: props.templateId},
|
||||
@ -23,6 +23,6 @@ const HorizontalGrip = React.memo((props: Props): JSX.Element => {
|
||||
onDoubleClick={() => props.onAutoSizeColumn(props.templateId)}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default HorizontalGrip
|
||||
export default React.memo(HorizontalGrip)
|
||||
|
@ -31,7 +31,7 @@ type Props = {
|
||||
onDropToGroup: (srcCard: Card, groupID: string, dstCardID: string) => void
|
||||
}
|
||||
|
||||
const TableGroup = React.memo((props: Props): JSX.Element => {
|
||||
const TableGroup = (props: Props): JSX.Element => {
|
||||
const {board, activeView, group, onDropToGroup, groupByProperty} = props
|
||||
const groupId = group.option.id
|
||||
|
||||
@ -86,6 +86,6 @@ const TableGroup = React.memo((props: Props): JSX.Element => {
|
||||
/>}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default TableGroup
|
||||
export default React.memo(TableGroup)
|
||||
|
@ -33,7 +33,7 @@ type Props = {
|
||||
onDrop: (srcOption: IPropertyOption, dstOption?: IPropertyOption) => void
|
||||
}
|
||||
|
||||
const TableGroupHeaderRow = React.memo((props: Props): JSX.Element => {
|
||||
const TableGroupHeaderRow = (props: Props): JSX.Element => {
|
||||
const {board, activeView, group, groupByProperty} = props
|
||||
const [groupTitle, setGroupTitle] = useState(group.option.value)
|
||||
|
||||
@ -147,6 +147,6 @@ const TableGroupHeaderRow = React.memo((props: Props): JSX.Element => {
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default TableGroupHeaderRow
|
||||
export default React.memo(TableGroupHeaderRow)
|
||||
|
@ -32,7 +32,7 @@ type Props = {
|
||||
onAutoSizeColumn: (columnID: string, headerWidth: number) => void
|
||||
}
|
||||
|
||||
const TableHeader = React.memo((props: Props): JSX.Element => {
|
||||
const TableHeader = (props: Props): JSX.Element => {
|
||||
const [isDragging, isOver, columnRef] = useSortable('column', props.template, !props.readonly, props.onDrop)
|
||||
|
||||
const columnWidth = (templateId: string): number => {
|
||||
@ -85,6 +85,6 @@ const TableHeader = React.memo((props: Props): JSX.Element => {
|
||||
}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default TableHeader
|
||||
export default React.memo(TableHeader)
|
||||
|
@ -43,7 +43,7 @@ export const columnWidth = (resizingColumn: string, columnWidths: Record<string,
|
||||
return Math.max(Constants.minColumnWidth, columnWidths[templateId] || 0)
|
||||
}
|
||||
|
||||
const TableRow = React.memo((props: Props) => {
|
||||
const TableRow = (props: Props) => {
|
||||
const {board, activeView, onSaveWithEnter, columnRefs, card} = props
|
||||
const contents = useAppSelector(getCardContents(card.id || ''))
|
||||
const comments = useAppSelector(getCardComments(card.id))
|
||||
@ -150,6 +150,6 @@ const TableRow = React.memo((props: Props) => {
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default TableRow
|
||||
export default React.memo(TableRow)
|
||||
|
@ -10,7 +10,7 @@ import HelpIcon from '../widgets/icons/help'
|
||||
import {Utils} from '../utils'
|
||||
import {Constants} from '../constants'
|
||||
|
||||
const TopBar = React.memo((): JSX.Element => {
|
||||
const TopBar = (): JSX.Element => {
|
||||
if (Utils.isFocalboardPlugin()) {
|
||||
const feedbackUrl = 'https://www.focalboard.com/fwlink/feedback-boards.html?v=' + Constants.versionString
|
||||
return (
|
||||
@ -65,6 +65,6 @@ const TopBar = React.memo((): JSX.Element => {
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default TopBar
|
||||
export default React.memo(TopBar)
|
||||
|
@ -38,7 +38,7 @@ exports[`components/viewHeader/filterValue return filterValue 1`] = `
|
||||
Status
|
||||
</div>
|
||||
<div
|
||||
class="Switch on"
|
||||
class="Switch size--small on"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
|
@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/viewHeader/viewHeaderActionsMenu return menu and verify call to board archive 1`] = `
|
||||
exports[`components/viewHeader/viewHeaderActionsMenu return menu 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ModalWrapper"
|
||||
@ -61,8 +61,67 @@ exports[`components/viewHeader/viewHeaderActionsMenu return menu and verify call
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
/>
|
||||
<div
|
||||
class="menu-options hideOnWidescreen"
|
||||
>
|
||||
<div
|
||||
aria-label="Share board"
|
||||
aria-label="Cancel"
|
||||
class="MenuOption TextOption menu-option menu-cancel"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/viewHeader/viewHeaderActionsMenu return menu and verify call to board archive 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ModalWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="View header menu"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="IconButton"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-dots-horizontal OptionsIcon"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="Menu noselect bottom"
|
||||
>
|
||||
<div
|
||||
class="menu-contents"
|
||||
>
|
||||
<div
|
||||
class="menu-options"
|
||||
>
|
||||
<div
|
||||
aria-label="Export to CSV"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
@ -72,7 +131,24 @@ exports[`components/viewHeader/viewHeaderActionsMenu return menu and verify call
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Share board
|
||||
Export to CSV
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Export board archive"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Export board archive
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
@ -111,226 +187,6 @@ exports[`components/viewHeader/viewHeaderActionsMenu return menu and verify call
|
||||
`;
|
||||
|
||||
exports[`components/viewHeader/viewHeaderActionsMenu return menu and verify call to csv exporter 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ModalWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="View header menu"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="IconButton"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-dots-horizontal OptionsIcon"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="Menu noselect bottom"
|
||||
>
|
||||
<div
|
||||
class="menu-contents"
|
||||
>
|
||||
<div
|
||||
class="menu-options"
|
||||
>
|
||||
<div
|
||||
aria-label="Export to CSV"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Export to CSV
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Export board archive"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Export board archive
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Share board"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Share board
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
/>
|
||||
<div
|
||||
class="menu-options hideOnWidescreen"
|
||||
>
|
||||
<div
|
||||
aria-label="Cancel"
|
||||
class="MenuOption TextOption menu-option menu-cancel"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/viewHeader/viewHeaderActionsMenu return menu with Share Boards 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ModalWrapper"
|
||||
>
|
||||
<div
|
||||
aria-label="View header menu"
|
||||
class="MenuWrapper"
|
||||
role="button"
|
||||
>
|
||||
<button
|
||||
class="IconButton"
|
||||
type="button"
|
||||
>
|
||||
<i
|
||||
class="CompassIcon icon-dots-horizontal OptionsIcon"
|
||||
/>
|
||||
</button>
|
||||
<div
|
||||
class="Menu noselect bottom"
|
||||
>
|
||||
<div
|
||||
class="menu-contents"
|
||||
>
|
||||
<div
|
||||
class="menu-options"
|
||||
>
|
||||
<div
|
||||
aria-label="Export to CSV"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Export to CSV
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Export board archive"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Export board archive
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
aria-label="Share board"
|
||||
class="MenuOption TextOption menu-option"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Share board
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="menu-spacer hideOnWidescreen"
|
||||
/>
|
||||
<div
|
||||
class="menu-options hideOnWidescreen"
|
||||
>
|
||||
<div
|
||||
aria-label="Cancel"
|
||||
class="MenuOption TextOption menu-option menu-cancel"
|
||||
role="button"
|
||||
>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
<div
|
||||
class="menu-name"
|
||||
>
|
||||
Cancel
|
||||
</div>
|
||||
<div
|
||||
class="noicon"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`components/viewHeader/viewHeaderActionsMenu return menu without Share Boards 1`] = `
|
||||
<div>
|
||||
<div
|
||||
class="ModalWrapper"
|
||||
|
@ -38,7 +38,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu 1
|
||||
Status
|
||||
</div>
|
||||
<div
|
||||
class="Switch"
|
||||
class="Switch size--small"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -59,7 +59,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu 1
|
||||
Property 1
|
||||
</div>
|
||||
<div
|
||||
class="Switch"
|
||||
class="Switch size--small"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -80,7 +80,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu 1
|
||||
Property 2
|
||||
</div>
|
||||
<div
|
||||
class="Switch"
|
||||
class="Switch size--small"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -101,7 +101,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu 1
|
||||
Property 3
|
||||
</div>
|
||||
<div
|
||||
class="Switch"
|
||||
class="Switch size--small"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -122,7 +122,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu 1
|
||||
Comments and Description
|
||||
</div>
|
||||
<div
|
||||
class="Switch"
|
||||
class="Switch size--small"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -198,7 +198,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu w
|
||||
Title
|
||||
</div>
|
||||
<div
|
||||
class="Switch"
|
||||
class="Switch size--small"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -219,7 +219,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu w
|
||||
Status
|
||||
</div>
|
||||
<div
|
||||
class="Switch"
|
||||
class="Switch size--small"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -240,7 +240,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu w
|
||||
Property 1
|
||||
</div>
|
||||
<div
|
||||
class="Switch"
|
||||
class="Switch size--small"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -261,7 +261,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu w
|
||||
Property 2
|
||||
</div>
|
||||
<div
|
||||
class="Switch"
|
||||
class="Switch size--small"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -282,7 +282,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu w
|
||||
Property 3
|
||||
</div>
|
||||
<div
|
||||
class="Switch"
|
||||
class="Switch size--small"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
@ -303,7 +303,7 @@ exports[`components/viewHeader/viewHeaderPropertiesMenu return properties menu w
|
||||
Comments and Description
|
||||
</div>
|
||||
<div
|
||||
class="Switch"
|
||||
class="Switch size--small"
|
||||
>
|
||||
<div
|
||||
class="octo-switch-inner"
|
||||
|
@ -20,7 +20,7 @@ type Props = {
|
||||
addCard: () => void
|
||||
}
|
||||
|
||||
const EmptyCardButton = React.memo((props: Props) => {
|
||||
const EmptyCardButton = (props: Props) => {
|
||||
const currentView = useAppSelector(getCurrentView)
|
||||
const intl = useIntl()
|
||||
|
||||
@ -52,6 +52,6 @@ const EmptyCardButton = React.memo((props: Props) => {
|
||||
</MenuWrapper>
|
||||
}
|
||||
/>)
|
||||
})
|
||||
}
|
||||
|
||||
export default EmptyCardButton
|
||||
export default React.memo(EmptyCardButton)
|
||||
|
@ -23,7 +23,7 @@ type Props = {
|
||||
onClose: () => void
|
||||
}
|
||||
|
||||
const FilterComponent = React.memo((props: Props): JSX.Element => {
|
||||
const FilterComponent = (props: Props): JSX.Element => {
|
||||
const conditionClicked = (optionId: string, filter: FilterClause): void => {
|
||||
const {activeView} = props
|
||||
|
||||
@ -91,6 +91,6 @@ const FilterComponent = React.memo((props: Props): JSX.Element => {
|
||||
</div>
|
||||
</Modal>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default FilterComponent
|
||||
export default React.memo(FilterComponent)
|
||||
|
@ -25,7 +25,7 @@ type Props = {
|
||||
filter: FilterClause
|
||||
}
|
||||
|
||||
const FilterEntry = React.memo((props: Props): JSX.Element => {
|
||||
const FilterEntry = (props: Props): JSX.Element => {
|
||||
const {board, view, filter} = props
|
||||
const intl = useIntl()
|
||||
|
||||
@ -106,6 +106,6 @@ const FilterEntry = React.memo((props: Props): JSX.Element => {
|
||||
</Button>
|
||||
</div>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default FilterEntry
|
||||
export default React.memo(FilterEntry)
|
||||
|
@ -22,7 +22,7 @@ type Props = {
|
||||
editCardTemplate: (cardTemplateId: string) => void
|
||||
}
|
||||
|
||||
const NewCardButton = React.memo((props: Props): JSX.Element => {
|
||||
const NewCardButton = (props: Props): JSX.Element => {
|
||||
const cardTemplates: Card[] = useAppSelector(getCurrentBoardTemplates)
|
||||
const currentView = useAppSelector(getCurrentView)
|
||||
const intl = useIntl()
|
||||
@ -79,6 +79,6 @@ const NewCardButton = React.memo((props: Props): JSX.Element => {
|
||||
</Menu>
|
||||
</ButtonWithMenu>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default NewCardButton
|
||||
export default React.memo(NewCardButton)
|
||||
|
@ -22,7 +22,7 @@ type Props = {
|
||||
editCardTemplate: (cardTemplateId: string) => void
|
||||
}
|
||||
|
||||
const NewCardButtonTemplateItem = React.memo((props: Props) => {
|
||||
const NewCardButtonTemplateItem = (props: Props) => {
|
||||
const currentView = useAppSelector(getCurrentView)
|
||||
const {cardTemplate} = props
|
||||
const intl = useIntl()
|
||||
@ -77,6 +77,6 @@ const NewCardButtonTemplateItem = React.memo((props: Props) => {
|
||||
}
|
||||
/>
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
export default NewCardButtonTemplateItem
|
||||
export default React.memo(NewCardButtonTemplateItem)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user