You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2026-01-29 07:46:13 +02:00
Compare commits
4 Commits
v2.4.7
...
server_tas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d71ac2e218 | ||
|
|
c35c5a5821 | ||
|
|
bcb08ac8a2 | ||
|
|
6ff8d775c2 |
Binary file not shown.
|
Before Width: | Height: | Size: 297 B |
@@ -137,8 +137,8 @@ fi
|
||||
#-----------------------------------------------------
|
||||
print 'Downloading Joplin...'
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
wget -O "${TEMP_DIR}/Joplin.AppImage" "https://github.com/laurent22/joplin/releases/download/v${RELEASE_VERSION}/Joplin-${RELEASE_VERSION}.AppImage"
|
||||
wget -O "${TEMP_DIR}/joplin.png" https://joplinapp.org/images/Icon512.png
|
||||
wget -O ${TEMP_DIR}/Joplin.AppImage https://github.com/laurent22/joplin/releases/download/v${RELEASE_VERSION}/Joplin-${RELEASE_VERSION}.AppImage
|
||||
wget -O ${TEMP_DIR}/joplin.png https://joplinapp.org/images/Icon512.png
|
||||
|
||||
#-----------------------------------------------------
|
||||
print 'Installing Joplin...'
|
||||
@@ -149,7 +149,7 @@ rm -f ~/.joplin/*.AppImage ~/.local/share/applications/joplin.desktop ~/.joplin/
|
||||
mkdir -p ~/.joplin/
|
||||
|
||||
# Download the latest version
|
||||
mv "${TEMP_DIR}/Joplin.AppImage" ~/.joplin/Joplin.AppImage
|
||||
mv ${TEMP_DIR}/Joplin.AppImage ~/.joplin/Joplin.AppImage
|
||||
|
||||
# Gives execution privileges
|
||||
chmod +x ~/.joplin/Joplin.AppImage
|
||||
@@ -159,7 +159,7 @@ print "${COLOR_GREEN}OK${COLOR_RESET}"
|
||||
#-----------------------------------------------------
|
||||
print 'Installing icon...'
|
||||
mkdir -p ~/.local/share/icons/hicolor/512x512/apps
|
||||
mv "${TEMP_DIR}/joplin.png" ~/.local/share/icons/hicolor/512x512/apps/joplin.png
|
||||
mv ${TEMP_DIR}/joplin.png ~/.local/share/icons/hicolor/512x512/apps/joplin.png
|
||||
print "${COLOR_GREEN}OK${COLOR_RESET}"
|
||||
|
||||
# Detect desktop environment
|
||||
@@ -222,7 +222,7 @@ fi
|
||||
print "${COLOR_GREEN}Joplin version${COLOR_RESET} ${RELEASE_VERSION} ${COLOR_GREEN}installed.${COLOR_RESET}"
|
||||
|
||||
# Record version
|
||||
echo "$RELEASE_VERSION" > ~/.joplin/VERSION
|
||||
echo $RELEASE_VERSION > ~/.joplin/VERSION
|
||||
|
||||
#-----------------------------------------------------
|
||||
if [[ "$SHOW_CHANGELOG" == true ]]; then
|
||||
@@ -232,5 +232,5 @@ fi
|
||||
|
||||
#-----------------------------------------------------
|
||||
print "Cleaning up..."
|
||||
rm -rf "$TEMP_DIR"
|
||||
rm -rf $TEMP_DIR
|
||||
print "${COLOR_GREEN}OK${COLOR_RESET}"
|
||||
|
||||
@@ -84,10 +84,4 @@ describe('HtmlToMd', function() {
|
||||
}
|
||||
}));
|
||||
|
||||
it('should allow disabling escape', async () => {
|
||||
const htmlToMd = new HtmlToMd();
|
||||
expect(htmlToMd.parse('https://test.com/1_2_3.pdf', { disableEscapeContent: true })).toBe('https://test.com/1_2_3.pdf');
|
||||
expect(htmlToMd.parse('https://test.com/1_2_3.pdf', { disableEscapeContent: false })).toBe('https://test.com/1\\_2\\_3.pdf');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -1 +1 @@
|
||||
X<sub>1</sub> X<sup>1</sup> <ins>Insert</ins> <span style="text-decoration: underline;">Insert alt</span> <s>Strike</s>
|
||||
X<sub>1</sub> X<sup>1</sup> <ins>Insert</ins> <s>Strike</s>
|
||||
@@ -1 +1 @@
|
||||
X<sub>1</sub> X<sup>1</sup> <ins>Insert</ins> <ins>Insert alt</ins> ~~Strike~~
|
||||
X<sub>1</sub> X<sup>1</sup> <ins>Insert</ins> ~~Strike~~
|
||||
@@ -19,11 +19,6 @@ export const runtime = (): CommandRuntime => {
|
||||
visible: !layoutItemProp(layout, 'noteList', 'visible'),
|
||||
});
|
||||
|
||||
// Toggling the sidebar will affect the size of most other on-screen components.
|
||||
// Dispatching a window resize event is a bit of a hack, but it ensures that any
|
||||
// component that watches for resizes will be accurately notified
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
|
||||
context.dispatch({
|
||||
type: 'MAIN_LAYOUT_SET',
|
||||
value: newLayout,
|
||||
|
||||
@@ -19,11 +19,6 @@ export const runtime = (): CommandRuntime => {
|
||||
visible: !layoutItemProp(layout, 'sideBar', 'visible'),
|
||||
});
|
||||
|
||||
// Toggling the sidebar will affect the size of most other on-screen components.
|
||||
// Dispatching a window resize event is a bit of a hack, but it ensures that any
|
||||
// component that watches for resizes will be accurately notified
|
||||
window.dispatchEvent(new Event('resize'));
|
||||
|
||||
context.dispatch({
|
||||
type: 'MAIN_LAYOUT_SET',
|
||||
value: newLayout,
|
||||
|
||||
@@ -6,8 +6,7 @@ import { EditorCommand, NoteBodyEditorProps } from '../../utils/types';
|
||||
import { commandAttachFileToBody, handlePasteEvent } from '../../utils/resourceHandling';
|
||||
import { ScrollOptions, ScrollOptionTypes } from '../../utils/types';
|
||||
import { CommandValue } from '../../utils/types';
|
||||
import { useScrollHandler, usePrevious, cursorPositionToTextOffset } from './utils';
|
||||
import useElementSize from '@joplin/lib/hooks/useElementSize';
|
||||
import { useScrollHandler, usePrevious, cursorPositionToTextOffset, useRootSize } from './utils';
|
||||
import Toolbar from './Toolbar';
|
||||
import styles_ from './styles';
|
||||
import { RenderedBody, defaultRenderedBody } from './utils/types';
|
||||
@@ -60,7 +59,7 @@ function CodeMirror(props: NoteBodyEditorProps, ref: any) {
|
||||
const props_onChangeRef = useRef<Function>(null);
|
||||
props_onChangeRef.current = props.onChange;
|
||||
|
||||
const rootSize = useElementSize(rootRef);
|
||||
const rootSize = useRootSize({ rootRef });
|
||||
|
||||
usePluginServiceRegistration(ref);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useEffect, useCallback, useRef } from 'react';
|
||||
import { useEffect, useCallback, useRef, useState } from 'react';
|
||||
import shim from '@joplin/lib/shim';
|
||||
|
||||
export function cursorPositionToTextOffset(cursorPos: any, body: string) {
|
||||
@@ -89,3 +89,21 @@ export function useScrollHandler(editorRef: any, webviewRef: any, onScroll: Func
|
||||
return { resetScroll, setEditorPercentScroll, setViewerPercentScroll, editor_scroll };
|
||||
}
|
||||
|
||||
|
||||
export function useRootSize(dependencies: any) {
|
||||
const { rootRef } = dependencies;
|
||||
|
||||
const [rootSize, setRootSize] = useState({ width: 0, height: 0 });
|
||||
|
||||
useEffect(() => {
|
||||
if (!rootRef.current) return;
|
||||
|
||||
const { width, height } = rootRef.current.getBoundingClientRect();
|
||||
|
||||
if (rootSize.width !== width || rootSize.height !== height) {
|
||||
setRootSize({ width: width, height: height });
|
||||
}
|
||||
});
|
||||
|
||||
return rootSize;
|
||||
}
|
||||
|
||||
@@ -171,7 +171,6 @@ export default function useKeymap(CodeMirror: any) {
|
||||
'Cmd-Right': 'goLineRightSmart',
|
||||
'Alt-Backspace': 'delGroupBefore',
|
||||
'Alt-Delete': 'delGroupAfter',
|
||||
'Cmd-Backspace': 'delWrappedLineLeft',
|
||||
|
||||
'fallthrough': 'basic',
|
||||
};
|
||||
|
||||
@@ -793,7 +793,7 @@ const TinyMCE = (props: NoteBodyEditorProps, ref: any) => {
|
||||
};
|
||||
}
|
||||
|
||||
await loadDocumentAssets(editor, await props.allAssets(props.contentMarkupLanguage, { contentMaxWidthTarget: '.mce-content-body' }));
|
||||
await loadDocumentAssets(editor, await props.allAssets(props.contentMarkupLanguage));
|
||||
|
||||
dispatchDidUpdate(editor);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,7 @@ import useMarkupToHtml from './utils/useMarkupToHtml';
|
||||
import useFormNote, { OnLoadEvent } from './utils/useFormNote';
|
||||
import useFolder from './utils/useFolder';
|
||||
import styles_ from './styles';
|
||||
import { NoteEditorProps, FormNote, ScrollOptions, ScrollOptionTypes, OnChangeEvent, NoteBodyEditorProps, AllAssetsOptions } from './utils/types';
|
||||
import { NoteEditorProps, FormNote, ScrollOptions, ScrollOptionTypes, OnChangeEvent, NoteBodyEditorProps } from './utils/types';
|
||||
import ResourceEditWatcher from '@joplin/lib/services/ResourceEditWatcher/index';
|
||||
import CommandService from '@joplin/lib/services/CommandService';
|
||||
import ToolbarButton from '../ToolbarButton/ToolbarButton';
|
||||
@@ -151,7 +151,7 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
plugins: props.plugins,
|
||||
});
|
||||
|
||||
const allAssets = useCallback(async (markupLanguage: number, options: AllAssetsOptions = null): Promise<any[]> => {
|
||||
const allAssets = useCallback(async (markupLanguage: number): Promise<any[]> => {
|
||||
const theme = themeStyle(props.themeId);
|
||||
|
||||
const markupToHtml = markupLanguageUtils.newMarkupToHtml({}, {
|
||||
@@ -159,10 +159,7 @@ function NoteEditor(props: NoteEditorProps) {
|
||||
customCss: props.customCss,
|
||||
});
|
||||
|
||||
return markupToHtml.allAssets(markupLanguage, theme, {
|
||||
contentMaxWidth: props.contentMaxWidth,
|
||||
contentMaxWidthTarget: options.contentMaxWidthTarget,
|
||||
});
|
||||
return markupToHtml.allAssets(markupLanguage, theme, { contentMaxWidth: props.contentMaxWidth });
|
||||
}, [props.themeId, props.customCss, props.contentMaxWidth]);
|
||||
|
||||
const handleProvisionalFlag = useCallback(() => {
|
||||
|
||||
@@ -69,17 +69,8 @@ export function htmlToClipboardData(html: string): ClipboardData {
|
||||
// In that case we need to set both HTML and Text context, otherwise it
|
||||
// won't be possible to paste the text in, for example, a text editor.
|
||||
// https://github.com/laurent22/joplin/issues/4788
|
||||
//
|
||||
// Also we don't escape the content produced in HTML to MD conversion
|
||||
// because it's not what would be expected. For example, if the content is
|
||||
// `* something`, strictly speaking it would be correct to escape to `\*
|
||||
// something`, however this is not what the user would expect when copying
|
||||
// text. Likewise for URLs that contain "_". So the resulting Markdown might
|
||||
// not be perfectly valid but would be closer to what a user would expect.
|
||||
// If they want accurate MArkdown they can switch to the MD editor.
|
||||
// https://github.com/laurent22/joplin/issues/5440
|
||||
return {
|
||||
text: htmlToMd().parse(copyableContent, { disableEscapeContent: true }),
|
||||
text: htmlToMd().parse(copyableContent),
|
||||
html: cleanUpCodeBlocks(copyableContent),
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,10 +6,6 @@ import { MarkupLanguage } from '@joplin/renderer';
|
||||
import { RenderResult, RenderResultPluginAsset } from '@joplin/renderer/MarkupToHtml';
|
||||
import { MarkupToHtmlOptions } from './useMarkupToHtml';
|
||||
|
||||
export interface AllAssetsOptions {
|
||||
contentMaxWidthTarget?: string;
|
||||
}
|
||||
|
||||
export interface ToolbarButtonInfos {
|
||||
[key: string]: ToolbarButtonInfo;
|
||||
}
|
||||
@@ -59,7 +55,7 @@ export interface NoteBodyEditorProps {
|
||||
onScroll(event: any): void;
|
||||
markupToHtml: (markupLanguage: MarkupLanguage, markup: string, options: MarkupToHtmlOptions)=> Promise<RenderResult>;
|
||||
htmlToMarkdown: Function;
|
||||
allAssets: (markupLanguage: MarkupLanguage, options: AllAssetsOptions)=> Promise<RenderResultPluginAsset[]>;
|
||||
allAssets: (markupLanguage: MarkupLanguage)=> Promise<RenderResultPluginAsset[]>;
|
||||
disabled: boolean;
|
||||
dispatch: Function;
|
||||
noteToolbar: any;
|
||||
|
||||
@@ -139,10 +139,9 @@ class NoteSearchBarComponent extends React.Component {
|
||||
color: theme.colorFaded,
|
||||
backgroundColor: theme.backgroundColor,
|
||||
});
|
||||
|
||||
const matchesFoundString = (query.length > 0) ? (
|
||||
const matchesFoundString = (query.length > 0 && this.props.resultCount > 0) ? (
|
||||
<div style={textStyle}>
|
||||
{`${this.props.resultCount === 0 ? 0 : this.props.selectedIndex + 1} / ${this.props.resultCount}`}
|
||||
{`${this.props.selectedIndex + 1} / ${this.props.resultCount}`}
|
||||
</div>
|
||||
) : null;
|
||||
|
||||
|
||||
4
packages/app-desktop/package-lock.json
generated
4
packages/app-desktop/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.4.7",
|
||||
"version": "2.4.6",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.4.7",
|
||||
"version": "2.4.6",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/app-desktop",
|
||||
"version": "2.4.7",
|
||||
"version": "2.4.6",
|
||||
"description": "Joplin for Desktop",
|
||||
"main": "main.js",
|
||||
"private": true,
|
||||
|
||||
@@ -251,12 +251,6 @@ export default class BaseApplication {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.indexOf('--user-data-dir=') === 0) {
|
||||
// Electron-specific flag. Allows users to run the app with chromedriver.
|
||||
argv.splice(0, 1);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (arg.length && arg[0] == '-') {
|
||||
throw new JoplinError(_('Unknown flag: %s', arg), 'flagError');
|
||||
} else {
|
||||
|
||||
@@ -6,7 +6,6 @@ export interface ParseOptions {
|
||||
anchorNames?: string[];
|
||||
preserveImageTagsWithSize?: boolean;
|
||||
baseUrl?: string;
|
||||
disableEscapeContent?: boolean;
|
||||
}
|
||||
|
||||
export default class HtmlToMd {
|
||||
@@ -21,7 +20,6 @@ export default class HtmlToMd {
|
||||
emDelimiter: '*',
|
||||
strongDelimiter: '**',
|
||||
br: '',
|
||||
disableEscapeContent: 'disableEscapeContent' in options ? options.disableEscapeContent : false,
|
||||
});
|
||||
turndown.use(turndownPluginGfm);
|
||||
turndown.remove('script');
|
||||
|
||||
@@ -479,31 +479,6 @@ export default class Synchronizer {
|
||||
void this.cancel();
|
||||
});
|
||||
|
||||
// ========================================================================
|
||||
// 2. DELETE_REMOTE
|
||||
// ------------------------------------------------------------------------
|
||||
// Delete the remote items that have been deleted locally.
|
||||
// ========================================================================
|
||||
|
||||
if (syncSteps.indexOf('delete_remote') >= 0) {
|
||||
const deletedItems = await BaseItem.deletedItems(syncTargetId);
|
||||
for (let i = 0; i < deletedItems.length; i++) {
|
||||
if (this.cancelling()) break;
|
||||
|
||||
const item = deletedItems[i];
|
||||
const path = BaseItem.systemPath(item.item_id);
|
||||
this.logSyncOperation('deleteRemote', null, { id: item.item_id }, 'local has been deleted');
|
||||
await this.apiCall('delete', path);
|
||||
|
||||
if (item.item_type === BaseModel.TYPE_RESOURCE) {
|
||||
const remoteContentPath = resourceRemotePath(item.item_id);
|
||||
await this.apiCall('delete', remoteContentPath);
|
||||
}
|
||||
|
||||
await BaseItem.remoteDeletedItem(syncTargetId, item.item_id);
|
||||
}
|
||||
} // DELETE_REMOTE STEP
|
||||
|
||||
// ========================================================================
|
||||
// 1. UPLOAD
|
||||
// ------------------------------------------------------------------------
|
||||
@@ -788,6 +763,31 @@ export default class Synchronizer {
|
||||
}
|
||||
} // UPLOAD STEP
|
||||
|
||||
// ========================================================================
|
||||
// 2. DELETE_REMOTE
|
||||
// ------------------------------------------------------------------------
|
||||
// Delete the remote items that have been deleted locally.
|
||||
// ========================================================================
|
||||
|
||||
if (syncSteps.indexOf('delete_remote') >= 0) {
|
||||
const deletedItems = await BaseItem.deletedItems(syncTargetId);
|
||||
for (let i = 0; i < deletedItems.length; i++) {
|
||||
if (this.cancelling()) break;
|
||||
|
||||
const item = deletedItems[i];
|
||||
const path = BaseItem.systemPath(item.item_id);
|
||||
this.logSyncOperation('deleteRemote', null, { id: item.item_id }, 'local has been deleted');
|
||||
await this.apiCall('delete', path);
|
||||
|
||||
if (item.item_type === BaseModel.TYPE_RESOURCE) {
|
||||
const remoteContentPath = resourceRemotePath(item.item_id);
|
||||
await this.apiCall('delete', remoteContentPath);
|
||||
}
|
||||
|
||||
await BaseItem.remoteDeletedItem(syncTargetId, item.item_id);
|
||||
}
|
||||
} // DELETE_REMOTE STEP
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 3. DELTA
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
@@ -386,34 +386,6 @@ describe('services_SearchFilter', function() {
|
||||
expect(ids(rows).sort()).toEqual(ids(notes0).concat(ids(notes00).concat(ids(notes1))).sort());
|
||||
}));
|
||||
|
||||
it('should support filtering and search term', (async () => {
|
||||
const notebook1 = await Folder.save({ title: 'notebook1' });
|
||||
const notebook2 = await Folder.save({ title: 'notebook2' });
|
||||
const note1 = await Note.save({ title: 'note1', body: 'abcdefg', parent_id: notebook1.id });
|
||||
await Note.save({ title: 'note2', body: 'body', parent_id: notebook1.id });
|
||||
await Note.save({ title: 'note3', body: 'abcdefg', parent_id: notebook2.id });
|
||||
await Note.save({ title: 'note4', body: 'body', parent_id: notebook2.id });
|
||||
|
||||
await engine.syncTables();
|
||||
|
||||
const testCases = [
|
||||
{ searchQuery: 'notebook:notebook1 abcdefg' },
|
||||
{ searchQuery: 'notebook:notebook1 "abcdefg"' },
|
||||
{ searchQuery: 'notebook:"notebook1" abcdefg' },
|
||||
{ searchQuery: 'notebook:"notebook1" "abcdefg"' },
|
||||
{ searchQuery: 'notebook:"notebook1" -tag:* "abcdefg"' },
|
||||
{ searchQuery: 'notebook:"notebook1" -tag:* abcdefg' },
|
||||
{ searchQuery: 'notebook:"notebook1" -tag:"*" abcdefg' },
|
||||
{ searchQuery: 'notebook:"notebook1" -tag:"*" "abcdefg"' },
|
||||
];
|
||||
|
||||
for (const testCase of testCases) {
|
||||
const rows = await engine.search(testCase.searchQuery, { searchType });
|
||||
expect(rows.length).toBe(1);
|
||||
expect(ids(rows)).toContain(note1.id);
|
||||
}
|
||||
}));
|
||||
|
||||
it('should support filtering by created date', (async () => {
|
||||
let rows;
|
||||
const n1 = await Note.save({ title: 'I made this on', body: 'May 20 2020', user_created_time: Date.parse('2020-05-20') });
|
||||
|
||||
@@ -36,7 +36,6 @@ const getTerms = (query: string, validFilters: Set<string>): Term[] => {
|
||||
if (inQuote) {
|
||||
terms.push(makeTerm(currentCol, currentTerm));
|
||||
currentTerm = '';
|
||||
currentCol = '_';
|
||||
inQuote = false;
|
||||
} else {
|
||||
inQuote = true;
|
||||
|
||||
@@ -9,7 +9,6 @@ function formatCssSize(v: any): string {
|
||||
|
||||
export interface Options {
|
||||
contentMaxWidth?: number;
|
||||
contentMaxWidthTarget?: string;
|
||||
}
|
||||
|
||||
export default function(theme: any, options: Options = null) {
|
||||
@@ -22,9 +21,8 @@ export default function(theme: any, options: Options = null) {
|
||||
|
||||
const fontFamily = '\'Avenir\', \'Arial\', sans-serif';
|
||||
|
||||
const maxWidthTarget = options.contentMaxWidthTarget ? options.contentMaxWidthTarget : '#rendered-md';
|
||||
const maxWidthCss = options.contentMaxWidth ? `
|
||||
${maxWidthTarget} {
|
||||
#rendered-md {
|
||||
max-width: ${options.contentMaxWidth}px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
|
||||
4
packages/server/package-lock.json
generated
4
packages/server/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@joplin/server",
|
||||
"version": "2.4.8",
|
||||
"version": "2.4.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@joplin/server",
|
||||
"version": "2.4.8",
|
||||
"version": "2.4.7",
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.15.1",
|
||||
"@koa/cors": "^3.1.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@joplin/server",
|
||||
"version": "2.4.8",
|
||||
"version": "2.4.7",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start-dev": "nodemon --config nodemon.json --ext ts,js,mustache,css,tsx dist/app.js --env dev",
|
||||
|
||||
@@ -196,34 +196,6 @@ describe('ItemModel', function() {
|
||||
expect((await models().user().load(user3.id)).total_item_size).toBe(totalSize3);
|
||||
});
|
||||
|
||||
test('should update total size when an item is deleted', async function() {
|
||||
const { user: user1 } = await createUserAndSession(1);
|
||||
|
||||
await createItemTree3(user1.id, '', '', [
|
||||
{
|
||||
id: '000000000000000000000000000000F1',
|
||||
children: [
|
||||
{
|
||||
id: '00000000000000000000000000000001',
|
||||
},
|
||||
],
|
||||
},
|
||||
]);
|
||||
|
||||
const folder1 = await models().item().loadByJopId(user1.id, '000000000000000000000000000000F1');
|
||||
const note1 = await models().item().loadByJopId(user1.id, '00000000000000000000000000000001');
|
||||
|
||||
await models().item().updateTotalSizes();
|
||||
|
||||
expect((await models().user().load(user1.id)).total_item_size).toBe(folder1.content_size + note1.content_size);
|
||||
|
||||
await models().item().delete(note1.id);
|
||||
|
||||
await models().item().updateTotalSizes();
|
||||
|
||||
expect((await models().user().load(user1.id)).total_item_size).toBe(folder1.content_size);
|
||||
});
|
||||
|
||||
test('should include shared items in total size calculation', async function() {
|
||||
const { user: user1, session: session1 } = await createUserAndSession(1);
|
||||
const { user: user2, session: session2 } = await createUserAndSession(2);
|
||||
|
||||
@@ -623,11 +623,7 @@ export default class ItemModel extends BaseModel<Item> {
|
||||
} else {
|
||||
const itemIds: Uuid[] = unique(changes.map(c => c.item_id));
|
||||
const userItems: UserItem[] = await this.db('user_items').select('user_id').whereIn('item_id', itemIds);
|
||||
const userIds: Uuid[] = unique(
|
||||
userItems
|
||||
.map(u => u.user_id)
|
||||
.concat(changes.map(c => c.user_id))
|
||||
);
|
||||
const userIds: Uuid[] = unique(userItems.map(u => u.user_id));
|
||||
|
||||
const totalSizes: TotalSizeRow[] = [];
|
||||
for (const userId of userIds) {
|
||||
|
||||
@@ -180,7 +180,7 @@ export default class UserModel extends BaseModel<User> {
|
||||
}
|
||||
|
||||
public async checkMaxItemSizeLimit(user: User, buffer: Buffer, item: Item, joplinItem: any) {
|
||||
// If the item is encrypted, we apply a multiplier because encrypted
|
||||
// If the item is encrypted, we apply a multipler because encrypted
|
||||
// items can be much larger (seems to be up to twice the size but for
|
||||
// safety let's go with 2.2).
|
||||
|
||||
@@ -198,20 +198,14 @@ export default class UserModel extends BaseModel<User> {
|
||||
));
|
||||
}
|
||||
|
||||
// We allow lock files to go through so that sync can happen, which in
|
||||
// turns allow user to fix oversized account by deleting items.
|
||||
const isWhiteListed = itemSize < 200 && item.name.startsWith('locks/');
|
||||
|
||||
if (!isWhiteListed) {
|
||||
// Also apply a multiplier to take into account E2EE overhead
|
||||
const maxTotalItemSize = getMaxTotalItemSize(user) * 1.5;
|
||||
if (maxTotalItemSize && user.total_item_size + itemSize >= maxTotalItemSize) {
|
||||
throw new ErrorPayloadTooLarge(_('Cannot save %s "%s" because it would go over the total allowed size (%s) for this account',
|
||||
isNote ? _('note') : _('attachment'),
|
||||
itemTitle ? itemTitle : item.name,
|
||||
formatBytes(maxTotalItemSize)
|
||||
));
|
||||
}
|
||||
// Also apply a multiplier to take into account E2EE overhead
|
||||
const maxTotalItemSize = getMaxTotalItemSize(user) * 1.5;
|
||||
if (maxTotalItemSize && user.total_item_size + itemSize >= maxTotalItemSize) {
|
||||
throw new ErrorPayloadTooLarge(_('Cannot save %s "%s" because it would go over the total allowed size (%s) for this account',
|
||||
isNote ? _('note') : _('attachment'),
|
||||
itemTitle ? itemTitle : item.name,
|
||||
formatBytes(maxTotalItemSize)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import Router from '../../utils/Router';
|
||||
import { RouteType } from '../../utils/types';
|
||||
import { AppContext } from '../../utils/types';
|
||||
import * as fs from 'fs-extra';
|
||||
import { ErrorForbidden, ErrorMethodNotAllowed, ErrorNotFound, ErrorPayloadTooLarge, errorToPlainObject } from '../../utils/errors';
|
||||
import { ErrorForbidden, ErrorMethodNotAllowed, ErrorNotFound, ErrorPayloadTooLarge } from '../../utils/errors';
|
||||
import ItemModel, { ItemSaveOption, SaveFromRawContentItem } from '../../models/ItemModel';
|
||||
import { requestDeltaPagination, requestPagination } from '../../models/utils/pagination';
|
||||
import { AclAction } from '../../models/BaseModel';
|
||||
@@ -66,9 +66,7 @@ export async function putItemContents(path: SubPath, ctx: AppContext, isBatch: b
|
||||
const output = await ctx.joplin.models.item().saveFromRawContent(ctx.joplin.owner, items, saveOptions);
|
||||
for (const [name] of Object.entries(output)) {
|
||||
if (output[name].item) output[name].item = ctx.joplin.models.item().toApiOutput(output[name].item) as Item;
|
||||
if (output[name].error) output[name].error = errorToPlainObject(output[name].error);
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
|
||||
@@ -114,17 +114,3 @@ export function errorToString(error: Error): string {
|
||||
if (error.stack) msg.push(error.stack);
|
||||
return msg.join(': ');
|
||||
}
|
||||
|
||||
interface PlainObjectError {
|
||||
httpCode?: number;
|
||||
message?: string;
|
||||
code?: string;
|
||||
}
|
||||
|
||||
export function errorToPlainObject(error: any): PlainObjectError {
|
||||
const output: PlainObjectError = {};
|
||||
if ('httpCode' in error) output.httpCode = error.httpCode;
|
||||
if ('code' in error) output.code = error.code;
|
||||
if ('message' in error) output.message = error.message;
|
||||
return output;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { repeat, isCodeBlockSpecialCase1, isCodeBlockSpecialCase2, isCodeBlock, getStyleProp } from './utilities'
|
||||
import { repeat, isCodeBlockSpecialCase1, isCodeBlockSpecialCase2, isCodeBlock } from './utilities'
|
||||
const Entities = require('html-entities').AllHtmlEntities;
|
||||
const htmlentities = (new Entities()).encode;
|
||||
|
||||
@@ -73,15 +73,7 @@ rules.highlight = {
|
||||
// HTML to avoid any ambiguity.
|
||||
|
||||
rules.insert = {
|
||||
filter: function (node, options) {
|
||||
// TinyMCE represents this either with an <INS> tag (when pressing the
|
||||
// toolbar button) or using style "text-decoration" (when using shortcut
|
||||
// Cmd+U)
|
||||
//
|
||||
// https://github.com/laurent22/joplin/issues/5480
|
||||
if (node.nodeName === 'INS') return true;
|
||||
return getStyleProp(node, 'text-decoration') === 'underline';
|
||||
},
|
||||
filter: 'ins',
|
||||
|
||||
replacement: function (content, node, options) {
|
||||
return '<ins>' + content + '</ins>'
|
||||
|
||||
@@ -23,7 +23,6 @@ export default function TurndownService (options) {
|
||||
linkReferenceStyle: 'full',
|
||||
anchorNames: [],
|
||||
br: ' ',
|
||||
disableEscapeContent: false,
|
||||
blankReplacement: function (content, node) {
|
||||
return node.isBlock ? '\n\n' : ''
|
||||
},
|
||||
@@ -182,8 +181,6 @@ TurndownService.prototype = {
|
||||
*/
|
||||
|
||||
function process (parentNode, escapeContent = 'auto') {
|
||||
if (this.options.disableEscapeContent) escapeContent = false;
|
||||
|
||||
var self = this
|
||||
return reduce.call(parentNode.childNodes, function (output, node) {
|
||||
node = new Node(node)
|
||||
|
||||
@@ -78,16 +78,3 @@ export function isCodeBlock(node) {
|
||||
node.firstChild.nodeName === 'CODE'
|
||||
)
|
||||
}
|
||||
|
||||
export function getStyleProp(node, name) {
|
||||
const style = node.getAttribute('style');
|
||||
if (!style) return null;
|
||||
|
||||
name = name.toLowerCase();
|
||||
if (!style.toLowerCase().includes(name)) return null;
|
||||
|
||||
const o = css.parse('div {' + style + '}');
|
||||
if (!o.stylesheet.rules.length) return null;
|
||||
const prop = o.stylesheet.rules[0].declarations.find(d => d.property.toLowerCase() === name);
|
||||
return prop ? prop.value : null;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
# Joplin Server Changelog
|
||||
|
||||
## [server-v2.4.8-beta](https://github.com/laurent22/joplin/releases/tag/server-v2.4.8-beta) (Pre-release) - 2021-09-15T22:16:59Z
|
||||
|
||||
- New: Added support for app level slow SQL query log (5e8b742)
|
||||
|
||||
## [server-v2.4.7-beta](https://github.com/laurent22/joplin/releases/tag/server-v2.4.7-beta) (Pre-release) - 2021-09-15T15:58:46Z
|
||||
|
||||
- Improved: Improve flag logic (c229821)
|
||||
|
||||
Reference in New Issue
Block a user