1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-27 20:29:45 +02:00

Compare commits

...

14 Commits

Author SHA1 Message Date
Laurent Cozic
3c897b2679 Server v2.0.7 2021-06-11 17:34:52 +02:00
Laurent Cozic
1ef9d1bf78 build docker image from ga 2021-06-11 17:33:56 +02:00
Nishant Mittal
c5c38a323f Desktop: Expose prompt to plugins as a command (#5058) 2021-06-11 00:26:16 +01:00
JackGruber
01e6ca4616 All: Fixes: Wrong field removed in API search (#5066) 2021-06-11 00:24:50 +01:00
Laurent Cozic
24a586c537 Merge branch 'dev' of github.com:laurent22/joplin into dev 2021-06-11 01:23:45 +02:00
Laurent Cozic
5d233a7387 Tools: Fixed tests 2021-06-11 01:15:43 +02:00
Helmut K. C. Tessarek
054e5428d5 All: Translation: Update da_DK.po (thanks ERYpTION) 2021-06-10 19:06:15 -04:00
Laurent
0120df7bdb Tools: Run CI on pull requests too 2021-06-10 23:24:44 +01:00
Laurent Cozic
a36b13dcb4 Server: Handle custom user content URLs 2021-06-10 19:33:04 +02:00
Laurent Cozic
b81c300907 Desktop release v2.0.8 2021-06-10 17:43:30 +02:00
Laurent Cozic
1ded589eeb Tools: Fixed macOS notarisation on CI 2021-06-10 17:43:09 +02:00
Laurent Cozic
315216132f Desktop release v2.0.7 2021-06-10 17:04:09 +02:00
Laurent Cozic
2eaa821272 Merge branch 'dev' into release-2.0 2021-06-10 17:03:29 +02:00
Laurent Cozic
7c93e268e4 Tools: Fixed CI for macOS build 2021-06-10 17:02:52 +02:00
33 changed files with 316 additions and 109 deletions

View File

@@ -278,6 +278,9 @@ packages/app-desktop/gui/MainScreen/commands/showNoteContentProperties.js.map
packages/app-desktop/gui/MainScreen/commands/showNoteProperties.d.ts
packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js
packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js.map
packages/app-desktop/gui/MainScreen/commands/showPrompt.d.ts
packages/app-desktop/gui/MainScreen/commands/showPrompt.js
packages/app-desktop/gui/MainScreen/commands/showPrompt.js.map
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.d.ts
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js.map
@@ -1601,6 +1604,9 @@ packages/renderer/pathUtils.js.map
packages/renderer/utils.d.ts
packages/renderer/utils.js
packages/renderer/utils.js.map
packages/tools/buildServerDocker.d.ts
packages/tools/buildServerDocker.js
packages/tools/buildServerDocker.js.map
packages/tools/generate-database-types.d.ts
packages/tools/generate-database-types.js
packages/tools/generate-database-types.js.map

View File

@@ -38,6 +38,7 @@ echo "GITHUB_REF=$GITHUB_REF"
echo "RUNNER_OS=$RUNNER_OS"
echo "GIT_TAG_NAME=$GIT_TAG_NAME"
echo "IS_CONTINUOUS_INTEGRATION=$IS_CONTINUOUS_INTEGRATION"
echo "IS_PULL_REQUEST=$IS_PULL_REQUEST"
echo "IS_DEV_BRANCH=$IS_DEV_BRANCH"
echo "IS_LINUX=$IS_LINUX"
@@ -122,6 +123,9 @@ cd "$ROOT_DIR/packages/app-desktop"
if [[ $GIT_TAG_NAME = v* ]]; then
USE_HARD_LINKS=false npm run dist
elif [[ $GIT_TAG_NAME = server-v* ]]; then
cd "$ROOT_DIR"
npm run buildServerDocker -- $GIT_TAG_NAME
else
USE_HARD_LINKS=false npm run dist -- --publish=never
fi

View File

@@ -1,5 +1,5 @@
name: Joplin Continuous Integration
on: [push]
on: [push, pull_request]
jobs:
Main:
runs-on: ${{ matrix.os }}
@@ -12,6 +12,7 @@ jobs:
# exist) since otherwise it will make the whole build fails, even though
# it might work without update. libsecret-1-dev is required for keytar -
# https://github.com/atom/node-keytar
- name: Install Linux dependencies
if: runner.os == 'Linux'
run: |
@@ -19,11 +20,27 @@ jobs:
sudo apt-get install -y gettext
sudo apt-get install -y libsecret-1-dev
- name: Install Docker Engine
if: runner.os == 'Linux'
run: |
sudo apt-get install -y apt-transport-https
sudo apt-get install -y ca-certificates
sudo apt-get install -y curl
sudo apt-get install -y gnupg
sudo apt-get install -y lsb-release
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
sudo apt-get install -y docker-ce docker-ce-cli containerd.io
- uses: actions/checkout@v2
- uses: olegtarasov/get-tag@v2.1
- uses: actions/setup-node@v2
with:
node-version: '12'
- uses: docker/login-action@v1
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Run script...
env:
@@ -33,5 +50,6 @@ jobs:
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
CSC_LINK: ${{ secrets.CSC_LINK }}
GH_TOKEN: ${{ secrets.GH_TOKEN }}
IS_CONTINUOUS_INTEGRATION: 1
run: |
"${GITHUB_WORKSPACE}/.github/scripts/run_ci.sh"

6
.gitignore vendored
View File

@@ -264,6 +264,9 @@ packages/app-desktop/gui/MainScreen/commands/showNoteContentProperties.js.map
packages/app-desktop/gui/MainScreen/commands/showNoteProperties.d.ts
packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js
packages/app-desktop/gui/MainScreen/commands/showNoteProperties.js.map
packages/app-desktop/gui/MainScreen/commands/showPrompt.d.ts
packages/app-desktop/gui/MainScreen/commands/showPrompt.js
packages/app-desktop/gui/MainScreen/commands/showPrompt.js.map
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.d.ts
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js
packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js.map
@@ -1587,6 +1590,9 @@ packages/renderer/pathUtils.js.map
packages/renderer/utils.d.ts
packages/renderer/utils.js
packages/renderer/utils.js.map
packages/tools/buildServerDocker.d.ts
packages/tools/buildServerDocker.js
packages/tools/buildServerDocker.js.map
packages/tools/generate-database-types.d.ts
packages/tools/generate-database-types.js
packages/tools/generate-database-types.js.map

View File

@@ -1,5 +1,3 @@
[![Travis Build Status](https://travis-ci.com/laurent22/joplin.svg?branch=master)](https://travis-ci.com/laurent22/joplin) [![Appveyor Build Status](https://ci.appveyor.com/api/projects/status/github/laurent22/joplin?branch=master&passingText=master%20-%20OK&svg=true)](https://ci.appveyor.com/project/laurent22/joplin)
# Building the applications
The Joplin source code is hosted on a [monorepo](https://en.wikipedia.org/wiki/Monorepo) managed by Lerna. The usage of Lerna is mostly transparent as the needed commands have been moved to the root package.json and thus are invoked for example when running `npm install` or `npm run watch`. The main thing to know about Lerna is that it links the packages in the monorepo using `npm link`, so if you check the node_modules directory you will see links instead of actual directories for certain packages. This is something to keep in mind as these links can cause issues in some cases.

View File

@@ -36,6 +36,7 @@
"releaseIOS": "node packages/tools/release-ios.js",
"releasePluginGenerator": "node packages/tools/release-plugin-generator.js",
"releaseServer": "node packages/tools/release-server.js",
"buildServerDocker": "node packages/tools/buildServerDocker.js",
"setupNewRelease": "node ./packages/tools/setupNewRelease",
"test-ci": "lerna run test-ci --stream",
"test": "lerna run test --stream",

View File

@@ -74,6 +74,7 @@ const commands = [
require('./gui/MainScreen/commands/toggleNoteList'),
require('./gui/MainScreen/commands/toggleSideBar'),
require('./gui/MainScreen/commands/toggleVisiblePanes'),
require('./gui/MainScreen/commands/showPrompt'),
require('./gui/NoteEditor/commands/focusElementNoteBody'),
require('./gui/NoteEditor/commands/focusElementNoteTitle'),
require('./gui/NoteEditor/commands/showLocalSearch'),

View File

@@ -135,6 +135,7 @@ const commands = [
require('./commands/openNote'),
require('./commands/openFolder'),
require('./commands/openTag'),
require('./commands/showPrompt'),
];
class MainScreenComponent extends React.Component<Props, State> {

View File

@@ -0,0 +1,41 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
export const declaration: CommandDeclaration = {
name: 'showPrompt',
};
enum PromptInputType {
Dropdown = 'dropdown',
Datetime = 'datetime',
Tags = 'tags',
Text = 'text',
}
interface PromptConfig {
label: string;
inputType?: PromptInputType;
value?: any;
autocomplete?: any[];
buttons?: string[];
}
export const runtime = (comp: any): CommandRuntime => {
return {
execute: async (_context: CommandContext, config: PromptConfig) => {
return new Promise((resolve) => {
comp.setState({
promptOptions: {
...config,
onClose: async (answer: any, buttonType: string) => {
comp.setState({ promptOptions: null });
resolve({
answer: answer,
buttonType: buttonType,
});
},
},
});
});
},
};
};

View File

@@ -95,7 +95,7 @@ export function ShareNoteDialog(props: Props) {
const copyLinksToClipboard = (shares: StateShare[]) => {
const links = [];
for (const share of shares) links.push(ShareService.instance().shareUrl(share));
for (const share of shares) links.push(ShareService.instance().shareUrl(ShareService.instance().userId, share));
clipboard.writeText(links.join('\n'));
};

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.0.6",
"version": "2.0.8",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/app-desktop",
"version": "2.0.6",
"version": "2.0.8",
"description": "Joplin for Desktop",
"main": "main.js",
"private": true,

View File

@@ -25,13 +25,13 @@ if [ "$RESET_ALL" == "1" ]; then
rm -rf "$PROFILE_DIR"
echo "config keychain.supported 0" >> "$CMD_FILE"
echo "config sync.target 9" >> "$CMD_FILE"
echo "config sync.9.path http://api-joplincloud.local:22300" >> "$CMD_FILE"
echo "config sync.9.username $USER_EMAIL" >> "$CMD_FILE"
echo "config sync.9.password 123456" >> "$CMD_FILE"
echo "config sync.target 10" >> "$CMD_FILE"
echo "config sync.10.path http://api.joplincloud.local:22300" >> "$CMD_FILE"
echo "config sync.10.username $USER_EMAIL" >> "$CMD_FILE"
echo "config sync.10.password 123456" >> "$CMD_FILE"
if [ "$USER_NUM" == "1" ]; then
curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' http://api-joplincloud.local:22300/api/debug
curl --data '{"action": "createTestUsers"}' -H 'Content-Type: application/json' http://api.joplincloud.local:22300/api/debug
echo 'mkbook "shared"' >> "$CMD_FILE"
echo 'mkbook "other"' >> "$CMD_FILE"

View File

@@ -27,7 +27,7 @@ module.exports = async function() {
// Use stdio: 'pipe' so that execSync doesn't print error directly to stdout
branch = execSync('git rev-parse --abbrev-ref HEAD', { stdio: 'pipe' }).toString().trim();
hash = execSync('git log --pretty="%h" -1', { stdio: 'pipe' }).toString().trim();
// The builds in CI are done from a 'detached HEAD' state, thus the branch name will be 'HEAD' for Travis builds.
// The builds in CI are done from a 'detached HEAD' state, thus the branch name will be 'HEAD' for CI builds.
} catch (err) {
// Don't display error object as it's a "fatal" error, but
// not for us, since is it not critical information

View File

@@ -25,8 +25,8 @@ module.exports = async function(params) {
console.info('Checking if notarization should be done...');
if (!process.env.TRAVIS || !process.env.TRAVIS_TAG) {
console.info(`Either not running in CI or not processing a tag - skipping notarization. process.env.TRAVIS = ${process.env.TRAVIS}; process.env.TRAVIS_TAG = ${process.env.TRAVIS}`);
if (!process.env.IS_CONTINUOUS_INTEGRATION || !process.env.GIT_TAG_NAME) {
console.info(`Either not running in CI or not processing a tag - skipping notarization. process.env.IS_CONTINUOUS_INTEGRATION = ${process.env.IS_CONTINUOUS_INTEGRATION}; process.env.GIT_TAG_NAME = ${process.env.GIT_TAG_NAME}`);
return;
}
@@ -45,9 +45,8 @@ module.exports = async function(params) {
console.log(`Notarizing ${appId} found at ${appPath}`);
// Every x seconds we print something to stdout, otherwise Travis will
// timeout the task after 10 minutes, and Apple notarization can take more
// time.
// Every x seconds we print something to stdout, otherwise CI may timeout
// the task after 10 minutes, and Apple notarization can take more time.
const waitingIntervalId = setInterval(() => {
console.log('.');
}, 60000);

View File

@@ -768,7 +768,7 @@ export default class BaseApplication {
if (Setting.value('env') === Env.Dev) {
Setting.setValue('sync.10.path', 'https://api.joplincloud.com');
Setting.setValue('sync.10.userContentPath', 'https://joplinusercontent.com');
// Setting.setValue('sync.10.path', 'http://api-joplincloud.local:22300');
// Setting.setValue('sync.10.path', 'http://api.joplincloud.local:22300');
// Setting.setValue('sync.10.userContentPath', 'http://joplinusercontent.local:22300');
}

View File

@@ -56,8 +56,14 @@ export default class JoplinServerApi {
return rtrimSlashes(this.options_.baseUrl());
}
public userContentBaseUrl() {
return this.options_.userContentBaseUrl() || this.baseUrl();
public userContentBaseUrl(userId: string) {
if (this.options_.userContentBaseUrl()) {
if (!userId) throw new Error('User ID must be specified');
const url = new URL(this.options_.userContentBaseUrl());
return `${url.protocol}//${userId.substr(0, 10).toLowerCase()}.${url.host}`;
} else {
return this.baseUrl();
}
}
private async session() {

View File

@@ -83,4 +83,25 @@ describe('services_SearchEngineUtils', function() {
expect(rows.map(r=>r.id)).toContain(todo2.id);
}));
});
it('remove auto added fields', (async () => {
await Note.save({ title: 'abcd', body: 'body 1' });
await searchEngine.syncTables();
const testCases = [
['title', 'todo_due'],
['title', 'todo_completed'],
['title'],
['title', 'todo_completed', 'todo_due'],
];
for (const testCase of testCases) {
const rows = await SearchEngineUtils.notesForQuery('abcd', false, { fields: [...testCase] }, searchEngine);
testCase.push('type_');
expect(Object.keys(rows[0]).length).toBe(testCase.length);
for (const field of testCase) {
expect(rows[0]).toHaveProperty(field);
}
}
}));
});

View File

@@ -38,10 +38,10 @@ export default class SearchEngineUtils {
isTodoAutoAdded = true;
}
let isTodoCompletedAutoAdded = false;
let todoCompletedAutoAdded = false;
if (fields.indexOf('todo_completed') < 0) {
fields.push('todo_completed');
isTodoCompletedAutoAdded = true;
todoCompletedAutoAdded = true;
}
const previewOptions = Object.assign({}, {
@@ -66,8 +66,8 @@ export default class SearchEngineUtils {
const idx = noteIds.indexOf(filteredNotes[i].id);
sortedNotes[idx] = filteredNotes[i];
if (idWasAutoAdded) delete sortedNotes[idx].id;
if (isTodoCompletedAutoAdded) delete sortedNotes[idx].is_todo;
if (isTodoAutoAdded) delete sortedNotes[idx].todo_completed;
if (todoCompletedAutoAdded) delete sortedNotes[idx].todo_completed;
if (isTodoAutoAdded) delete sortedNotes[idx].is_todo;
}
// Note that when the search engine index is somehow corrupted, it might contain

View File

@@ -33,6 +33,10 @@ export default class ShareService {
return this.store.getState()[stateRootKey] as State;
}
public get userId(): string {
return this.api() ? this.api().userId : '';
}
private api(): JoplinServerApi {
if (this.api_) return this.api_;
@@ -136,8 +140,8 @@ export default class ShareService {
await Note.save({ id: note.id, is_shared: 0 });
}
public shareUrl(share: StateShare): string {
return `${this.api().userContentBaseUrl()}/shares/${share.id}`;
public shareUrl(userId: string, share: StateShare): string {
return `${this.api().userContentBaseUrl(userId)}/shares/${share.id}`;
}
public get shares() {

View File

@@ -577,7 +577,7 @@ async function initFileApi() {
// const joplinServerAuth = {
// "email": "admin@localhost",
// "password": "admin",
// "baseUrl": "http://api-joplincloud.local:22300",
// "baseUrl": "http://api.joplincloud.local:22300",
// "userContentBaseUrl": ""
// }

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/server",
"version": "2.0.6",
"version": "2.0.7",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@joplin/server",
"version": "2.0.6",
"version": "2.0.7",
"private": true,
"scripts": {
"start-dev": "nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/app.js --env dev",

View File

@@ -5,6 +5,7 @@ import { unique } from '../utils/array';
import { ErrorBadRequest, ErrorForbidden, ErrorNotFound } from '../utils/errors';
import { setQueryParameters } from '../utils/urlUtils';
import BaseModel, { AclAction, DeleteOptions, ValidateOptions } from './BaseModel';
import { userIdFromUserContentUrl } from '../utils/routeUtils';
export default class ShareModel extends BaseModel<Share> {
@@ -33,6 +34,19 @@ export default class ShareModel extends BaseModel<Share> {
}
}
public checkShareUrl(share: Share, shareUrl: string) {
if (this.baseUrl === this.userContentUrl) return; // OK
const userId = userIdFromUserContentUrl(shareUrl);
const shareUserId = share.owner_id.toLowerCase();
if (userId.length >= 10 && shareUserId.indexOf(userId) === 0) {
// OK
} else {
throw new ErrorBadRequest('Invalid origin (User Content)');
}
}
protected objectToApiOutput(object: Share): Share {
const output: Share = {};

View File

@@ -36,6 +36,8 @@ router.get('shares/:id', async (path: SubPath, ctx: AppContext) => {
const result = await renderItem(ctx, item, share);
ctx.models.share().checkShareUrl(share, ctx.URL.origin);
ctx.response.body = result.body;
ctx.response.set('Content-Type', result.mime);
ctx.response.set('Content-Length', result.size.toString());

View File

@@ -1,5 +1,6 @@
import { isValidOrigin, parseSubPath, splitItemPath } from './routeUtils';
import { ItemAddressingType } from '../db';
import { RouteType } from './types';
describe('routeUtils', function() {
@@ -41,7 +42,7 @@ describe('routeUtils', function() {
}
});
it('should check the request origin', async function() {
it('should check the request origin for API URLs', async function() {
const testCases: any[] = [
[
'https://example.com', // Request origin
@@ -79,7 +80,37 @@ describe('routeUtils', function() {
for (const testCase of testCases) {
const [requestOrigin, configBaseUrl, expected] = testCase;
expect(isValidOrigin(requestOrigin, configBaseUrl)).toBe(expected);
expect(isValidOrigin(requestOrigin, configBaseUrl, RouteType.Api)).toBe(expected);
}
});
it('should check the request origin for User Content URLs', async function() {
const testCases: any[] = [
[
'https://usercontent.local', // Request origin
'https://usercontent.local', // Config base URL
true,
],
[
'http://usercontent.local',
'https://usercontent.local',
true,
],
[
'https://abcd.usercontent.local',
'https://usercontent.local',
true,
],
[
'https://bad.local',
'https://usercontent.local',
false,
],
];
for (const testCase of testCases) {
const [requestOrigin, configBaseUrl, expected] = testCase;
expect(isValidOrigin(requestOrigin, configBaseUrl, RouteType.UserContent)).toBe(expected);
}
});

View File

@@ -1,5 +1,5 @@
import { baseUrl } from '../config';
import { Item, ItemAddressingType } from '../db';
import { Item, ItemAddressingType, Uuid } from '../db';
import { ErrorBadRequest, ErrorForbidden, ErrorNotFound } from './errors';
import Router from './Router';
import { AppContext, HttpMethod, RouteType } from './types';
@@ -153,19 +153,30 @@ export function parseSubPath(basePath: string, p: string, rawPath: string = null
return output;
}
export function isValidOrigin(requestOrigin: string, endPointBaseUrl: string): boolean {
export function isValidOrigin(requestOrigin: string, endPointBaseUrl: string, routeType: RouteType): boolean {
const host1 = (new URL(requestOrigin)).host;
const host2 = (new URL(endPointBaseUrl)).host;
return host1 === host2;
if (routeType === RouteType.UserContent) {
// At this point we only check if eg usercontent.com has been accessed
// with origin usercontent.com, or something.usercontent.com. We don't
// check that the user ID is valid or is event present. This will be
// done by the /share end point, which will also check that the share
// owner ID matches the origin URL.
if (host1 === host2) return true;
const hostNoPrefix = host1.split('.').slice(1).join('.');
return hostNoPrefix === host2;
} else {
return host1 === host2;
}
}
export function userIdFromUserContentUrl(url: string): Uuid {
const s = (new URL(url)).hostname.split('.');
return s[0].toLowerCase();
}
export function routeResponseFormat(context: AppContext): RouteResponseFormat {
// const rawPath = context.path;
// if (match && match.route.responseFormat) return match.route.responseFormat;
// let path = rawPath;
// if (match) path = match.basePath ? match.basePath : match.subPath.raw;
const path = context.path;
return path.indexOf('api') === 0 || path.indexOf('/api') === 0 ? RouteResponseFormat.Json : RouteResponseFormat.Html;
}
@@ -175,7 +186,7 @@ export async function execRequest(routes: Routers, ctx: AppContext) {
if (!match) throw new ErrorNotFound();
const endPoint = match.route.findEndPoint(ctx.request.method as HttpMethod, match.subPath.schema);
if (ctx.URL && !isValidOrigin(ctx.URL.origin, baseUrl(endPoint.type))) throw new ErrorNotFound('Invalid origin', 'invalidOrigin');
if (ctx.URL && !isValidOrigin(ctx.URL.origin, baseUrl(endPoint.type), endPoint.type)) throw new ErrorNotFound('Invalid origin', 'invalidOrigin');
// This is a generic catch-all for all private end points - if we
// couldn't get a valid session, we exit now. Individual end points

View File

@@ -23,7 +23,7 @@ async function setupServices(env: Env, models: Models, config: Config): Promise<
return output;
}
export default async function(appContext: AppContext, env: Env, dbConnection: DbConnection, appLogger: ()=> LoggerWrapper) {
export default async function(appContext: AppContext, env: Env, dbConnection: DbConnection, appLogger: ()=> LoggerWrapper): Promise<AppContext> {
appContext.env = env;
appContext.db = dbConnection;
appContext.models = newModelFactory(appContext.db, config());
@@ -32,4 +32,6 @@ export default async function(appContext: AppContext, env: Env, dbConnection: Db
appContext.routes = { ...routes };
if (env === Env.Prod) delete appContext.routes['api/debug'];
return appContext;
}

View File

@@ -177,24 +177,24 @@ export async function koaAppContext(options: AppContextTestOptions = null): Prom
// Set type to "any" because the Koa context has many properties and we
// don't need to mock all of them.
const appContext: any = {};
await setupAppContext(appContext, Env.Dev, db_, () => appLogger);
appContext.env = Env.Dev;
appContext.db = db_;
appContext.models = models();
appContext.appLogger = () => appLogger;
appContext.path = req.url;
appContext.owner = owner;
appContext.cookies = new FakeCookies();
appContext.request = new FakeRequest(req);
appContext.response = new FakeResponse();
appContext.headers = { ...reqOptions.headers };
appContext.req = req;
appContext.query = req.query;
appContext.method = req.method;
appContext.redirect = () => {};
const appContext: any = {
...await setupAppContext({} as any, Env.Dev, db_, () => appLogger),
env: Env.Dev,
db: db_,
models: models(),
appLogger: () => appLogger,
path: req.url,
owner: owner,
cookies: new FakeCookies(),
request: new FakeRequest(req),
response: new FakeResponse(),
headers: { ...reqOptions.headers },
req: req,
query: req.query,
method: req.method,
redirect: () => {},
URL: { origin: config().baseUrl },
};
if (options.sessionId) {
appContext.cookies.set('sessionId', options.sessionId);

View File

@@ -0,0 +1,39 @@
import { execCommand2, rootDir } from './tool-utils';
function getVersionFromTag(tagName: string): string {
if (tagName.indexOf('server-') !== 0) throw new Error(`Invalid tag: ${tagName}`);
const s = tagName.split('-');
return s[1];
}
function getIsPreRelease(tagName: string): boolean {
return tagName.indexOf('-beta') > 0;
}
async function main() {
const argv = require('yargs').argv;
if (!argv.tagName) throw new Error('--tag-name not provided');
const tagName = argv.tagName;
const imageVersion = getVersionFromTag(tagName);
const isPreRelease = getIsPreRelease(tagName);
process.chdir(rootDir);
console.info(`Running from: ${process.cwd()}`);
console.info('tagName:', tagName);
console.info('imageVersion:', imageVersion);
console.info('isPreRelease:', isPreRelease);
await execCommand2(`docker build -t "joplin/server:${imageVersion}" -f Dockerfile.server .`);
await execCommand2(`docker tag "joplin/server:${imageVersion}" "joplin/server:latest"`);
await execCommand2(`docker push joplin/server:${imageVersion}`);
if (!isPreRelease) await execCommand2('docker push joplin/server:latest');
}
main().catch((error) => {
console.error('Fatal error');
console.error(error);
process.exit(1);
});

View File

@@ -15,6 +15,8 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.4.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
#: packages/app-desktop/bridge.js:106 packages/app-desktop/bridge.js:110
#: packages/app-desktop/bridge.js:126 packages/app-desktop/bridge.js:134
@@ -121,11 +123,11 @@ msgstr "Download"
#: packages/app-desktop/checkForUpdates.js:189
msgid "Skip this version"
msgstr ""
msgstr "Spring denne version over"
#: packages/app-desktop/checkForUpdates.js:189
msgid "Full changelog"
msgstr ""
msgstr "Komplet ændringslog"
#: packages/app-desktop/gui/NoteRevisionViewer.min.js:75
#, javascript-format
@@ -274,7 +276,6 @@ msgid "Advanced tools"
msgstr "Avancerede indstillinger"
#: packages/app-desktop/gui/StatusScreen/StatusScreen.js:136
#, fuzzy
msgid "Export debug report"
msgstr "Eksporter fejlrapport"
@@ -763,15 +764,15 @@ msgstr "Mere information"
#: packages/app-desktop/gui/MainScreen/MainScreen.js:476
#, javascript-format
msgid "%s (%s) would like to share a notebook with you."
msgstr ""
msgstr "%s (%s) vil gerne dele en notesbog med dig."
#: packages/app-desktop/gui/MainScreen/MainScreen.js:478
msgid "Accept"
msgstr ""
msgstr "Acceptér"
#: packages/app-desktop/gui/MainScreen/MainScreen.js:480
msgid "Reject"
msgstr ""
msgstr "Afvis"
#: packages/app-desktop/gui/MainScreen/MainScreen.js:484
msgid "Some items cannot be synchronised."
@@ -857,9 +858,8 @@ msgid "Toggle editors"
msgstr "Skift editorer"
#: packages/app-desktop/gui/MainScreen/commands/showShareFolderDialog.js:16
#, fuzzy
msgid "Share notebook..."
msgstr "Del note..."
msgstr "Del notesbog..."
#: packages/app-desktop/gui/MainScreen/commands/toggleLayoutMoveMode.js:16
msgid "Change application layout"
@@ -1511,13 +1511,12 @@ msgid "You do not have any installed plugin."
msgstr "Du har ikke installeret nogen udvidelser."
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:232
#, fuzzy
msgid "Could not connect to plugin repository"
msgstr "Kunne ikke installere plugin: %s"
msgstr "Kunne ikke forbinde til plugin-lager"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:234
msgid "Try again"
msgstr ""
msgstr "Prøv igen"
#: packages/app-desktop/gui/ConfigScreen/controls/plugins/PluginsStates.js:242
msgid "Plugin tools"
@@ -1706,9 +1705,8 @@ msgstr ""
"(Begrænsning: %s)."
#: packages/app-desktop/gui/ShareNoteDialog.js:141
#, fuzzy
msgid "Unshare note"
msgstr "Del"
msgstr "Del ikke længere note"
#: packages/app-desktop/gui/ShareNoteDialog.js:168
msgid "Synchronising..."
@@ -1745,19 +1743,20 @@ msgstr[0] "Kopier link til deling"
msgstr[1] "Kopier links til deling"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:138
#, fuzzy
msgid "Unshare"
msgstr "Del"
msgstr "Del ikke længere"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:180
msgid ""
"Delete this invitation? The recipient will no longer have access to this "
"shared notebook."
msgstr ""
"Vil du slette denne invitation? Modtageren vil ikke længere have adgang til "
"denne delte notesbog."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:194
msgid "Add recipient:"
msgstr ""
msgstr "Tilføj modtager:"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:197
#: packages/app-mobile/components/NoteBodyViewer/hooks/useOnResourceLongPress.js:28
@@ -1767,19 +1766,19 @@ msgstr "Del"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:206
msgid "Recipient has not yet accepted the invitation"
msgstr ""
msgstr "Modtager har endnu ikke accepteret invitationen"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:207
msgid "Recipient has rejected the invitation"
msgstr ""
msgstr "Modtageren har afslået invitationen"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:208
msgid "Recipient has accepted the invitation"
msgstr ""
msgstr "Modtageren har accepteret invitationen"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:218
msgid "Recipients:"
msgstr ""
msgstr "Modtagere:"
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:230
#, fuzzy
@@ -1787,20 +1786,20 @@ msgid "Synchronizing..."
msgstr "Synkroniserer..."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:231
#, fuzzy
msgid "Sharing notebook..."
msgstr "Del note..."
msgstr "Deler notesbog..."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:241
msgid ""
"Unshare this notebook? The recipients will no longer have access to its "
"content."
msgstr ""
"Del ikke længere denne notesbog? Modtagerne vil ikke længere have adgang til "
"dens indhold."
#: packages/app-desktop/gui/ShareFolderDialog/ShareFolderDialog.js:251
#, fuzzy
msgid "Share Notebook"
msgstr "Del noter"
msgstr "Del notesbog"
#: packages/app-desktop/commands/toggleSafeMode.js:18
msgid "Toggle safe mode"
@@ -2248,15 +2247,14 @@ msgstr ""
"nu](%s)"
#: packages/server/dist/models/UserModel.js:134
#, fuzzy
msgid "attachment"
msgstr "Vedhæftninger"
msgstr "vedhæftning"
#: packages/server/dist/models/UserModel.js:134
#, javascript-format
msgid ""
"Cannot save %s \"%s\" because it is larger than than the allowed limit (%s)"
msgstr ""
msgstr "Kan ikke gemme %s \"%s\" da den er større end den tilladte grænse (%s)"
#: packages/lib/onedrive-api-node-utils.js:46
#, javascript-format
@@ -2440,23 +2438,20 @@ msgid "Joplin Server URL"
msgstr "Joplin Server URL"
#: packages/lib/models/Setting.js:321
#, fuzzy
msgid "Joplin Server email"
msgstr "Joplin server"
msgstr "Joplin server e-mail"
#: packages/lib/models/Setting.js:332
msgid "Joplin Server password"
msgstr "Joplin Server kodeord"
#: packages/lib/models/Setting.js:353
#, fuzzy
msgid "Joplin Cloud email"
msgstr "Joplin server"
msgstr "Joplin Cloud e-mail"
#: packages/lib/models/Setting.js:364
#, fuzzy
msgid "Joplin Cloud password"
msgstr "Joplin Server kodeord"
msgstr "Joplin Cloud adgangskode"
#: packages/lib/models/Setting.js:376
msgid "Attachment download behaviour"
@@ -2723,11 +2718,11 @@ msgstr "Brugerdefineret stylesheet til Joplin app-stil"
#: packages/lib/models/Setting.js:794
msgid "Re-upload local data to sync target"
msgstr ""
msgstr "Upload lokal data igen til synkroniseringsmål"
#: packages/lib/models/Setting.js:804
msgid "Delete local data and re-download from sync target"
msgstr ""
msgstr "Slet lokal data og download igen fra synkroniseringsmål"
#: packages/lib/models/Setting.js:809
msgid "Automatically update the application"
@@ -3047,9 +3042,8 @@ msgid "Encrypted items cannot be modified"
msgstr "Krypteret emner kan ikke rettes"
#: packages/lib/SyncTargetJoplinCloud.js:28
#, fuzzy
msgid "Joplin Cloud"
msgstr "Joplin-forum"
msgstr "Joplin Cloud"
#: packages/lib/BaseApplication.js:152 packages/lib/BaseApplication.js:164
#: packages/lib/BaseApplication.js:196
@@ -3309,14 +3303,14 @@ msgid "HTML Directory"
msgstr "HTML Indeks"
#: packages/lib/services/interop/InteropService.js:127
#, fuzzy, javascript-format
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\" and output \"%s\""
msgstr "Kan ikke indlæse \"%s\" modul for format \"%s\" og output \"%s\""
msgstr "Kan ikke indlæse \"%s\" modul til format \"%s\" og output \"%s\""
#: packages/lib/services/interop/InteropService.js:150
#, fuzzy, javascript-format
#, javascript-format
msgid "Cannot load \"%s\" module for format \"%s\" and target \"%s\""
msgstr "Kan ikke indlæse \"%s\" modul for format \"%s\" og mål \"%s\""
msgstr "Kan ikke indlæse \"%s\" modul til format \"%s\" og mål \"%s\""
#: packages/lib/services/interop/InteropService.js:194
#: packages/lib/services/interop/InteropService.js:206
@@ -3878,6 +3872,7 @@ msgid ""
"Runs the commands contained in the text file. There should be one command "
"per line."
msgstr ""
"Afvikler kommandoerne i tekstfilen. Der skal være én kommando pr. linje."
#: packages/app-cli/app/command-version.js:11
msgid "Displays version information"

View File

@@ -11,18 +11,18 @@ async function main() {
process.chdir(serverDir);
const version = (await execCommand2('npm version patch')).trim();
const versionShort = version.substr(1);
const imageVersion = versionShort + (isPreRelease ? '-beta' : '');
// const versionShort = version.substr(1);
// const imageVersion = versionShort + (isPreRelease ? '-beta' : '');
const tagName = `server-${version}`;
process.chdir(rootDir);
console.info(`Running from: ${process.cwd()}`);
// process.chdir(rootDir);
// console.info(`Running from: ${process.cwd()}`);
await execCommand2(`docker build -t "joplin/server:${imageVersion}" -f Dockerfile.server .`);
await execCommand2(`docker tag "joplin/server:${imageVersion}" "joplin/server:latest"`);
await execCommand2(`docker push joplin/server:${imageVersion}`);
// await execCommand2(`docker build -t "joplin/server:${imageVersion}" -f Dockerfile.server .`);
// await execCommand2(`docker tag "joplin/server:${imageVersion}" "joplin/server:latest"`);
// await execCommand2(`docker push joplin/server:${imageVersion}`);
if (!isPreRelease) await execCommand2('docker push joplin/server:latest');
// if (!isPreRelease) await execCommand2('docker push joplin/server:latest');
const changelogPath = `${rootDir}/readme/changelog_server.md`;
await completeReleaseWithChangelog(changelogPath, version, tagName, 'Server', isPreRelease);

View File

@@ -1,5 +1,12 @@
# Joplin Server Changelog
## [server-v2.0.7](https://github.com/laurent22/joplin/releases/tag/server-v2.0.7) (Pre-release) - 2021-06-11T15:34:30Z
- New: Add navbar on login and sign up page (7a3a208)
- New: Added option to enable or disable stack traces (5614eb9)
- Improved: Handle custom user content URLs (a36b13d)
- Fixed: Fixed error when creating user (594084e)
## [server-v2.0.6](https://github.com/laurent22/joplin/releases/tag/server-v2.0.6) (Pre-release) - 2021-06-07T17:27:27Z
- New: Add Stripe integration (770af6a)