1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-09-02 20:46:21 +02:00

Compare commits

...

9 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
28 changed files with 307 additions and 99 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

@@ -123,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:

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

@@ -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

@@ -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

@@ -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)