mirror of
https://github.com/ether/etherpad-lite.git
synced 2026-07-18 00:57:55 +00:00
Add creator-owned pad settings defaults (#7545)
* Add creator-owned pad settings defaults Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Refine pad settings layout Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix settings popup heading and width Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Explain enforced user settings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Cover creator override flow Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Let creators bypass enforced settings Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address pad settings follow-ups Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
2f0b5b0fca
commit
e0ccdb4d9f
19 changed files with 773 additions and 219 deletions
|
|
@ -5,7 +5,7 @@ Cookies used by Etherpad.
|
|||
| Name | Sample value | Domain | Path | Expires/max-age | Http-only | Secure | Usage description |
|
||||
|-------------------|----------------------------------|-------------|------|-----------------|-----------|--------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| express_sid | s%3A7yCNjRmTW8ylGQ53I2IhOwYF9... | example.org | / | Session | true | true | Session ID of the [Express web framework](https://expressjs.com). When Etherpad is behind a reverse proxy, and an administrator wants to use session stickiness, he may use this cookie. If you are behind a reverse proxy, please remember to set `trustProxy: true` in `settings.json`. Set in [webaccess.js#L131](https://github.com/ether/etherpad-lite/blob/01497aa399690e44393e91c19917d11d025df71b/src/node/hooks/express/webaccess.js#L131). |
|
||||
| language | en | example.org | / | Session | false | true | The language of the UI (e.g.: `en-GB`, `it`). Set in [pad_editor.js#L111](https://github.com/ether/etherpad-lite/blob/01497aa399690e44393e91c19917d11d025df71b/src/static/js/pad_editor.js#L111). |
|
||||
| language | en | example.org | / | Session | false | true | The language of the UI (e.g.: `en-GB`, `it`). Set by the pad client when the user changes **My View → Language** (currently in `src/static/js/pad.ts`, via `setMyViewLanguage()`). |
|
||||
| prefs / prefsHttp | %7B%22epThemesExtTheme%22... | example.org | /p | year 3000 | false | true | Client-side preferences (e.g.: font family, chat always visible, show authorship colors, ...). Set in [pad_cookie.js#L49](https://github.com/ether/etherpad-lite/blob/01497aa399690e44393e91c19917d11d025df71b/src/static/js/pad_cookie.js#L49). `prefs` is used if Etherpad is accessed over HTTPS, `prefsHttp` if accessed over HTTP. For more info see https://github.com/ether/etherpad-lite/issues/3179. |
|
||||
| token | t.tFzkihhhBf4xKEpCK3PU | example.org | / | 60 days | false | true | A random token representing the author, of the form `t.randomstring_of_lenght_20`. The random string is generated by the client, at ([pad.js#L55-L66](https://github.com/ether/etherpad-lite/blob/01497aa399690e44393e91c19917d11d025df71b/src/static/js/pad.js#L55-L66)). This cookie is always set by the client (at [pad.js#L153-L158](https://github.com/ether/etherpad-lite/blob/01497aa399690e44393e91c19917d11d025df71b/src/static/js/pad.js#L153-L158)) without any solicitation from the server. It is used for all the pads accessed via the web UI (not used for the HTTP API). On the server side, its value is accessed at [SecurityManager.js#L33](https://github.com/ether/etherpad-lite/blob/01497aa399690e44393e91c19917d11d025df71b/src/node/db/SecurityManager.js#L33). |
|
||||
|
||||
|
|
|
|||
|
|
@ -205,6 +205,12 @@
|
|||
**/
|
||||
"enableDarkMode": "${ENABLE_DARK_MODE:true}",
|
||||
|
||||
/**
|
||||
* Enable creator-owned Pad-wide Settings and new-pad default seeding from My View.
|
||||
* Disabled by default to preserve the legacy single-settings behavior.
|
||||
**/
|
||||
"enablePadWideSettings": "${ENABLE_PAD_WIDE_SETTINGS:false}",
|
||||
|
||||
/*
|
||||
* Node native SSL support
|
||||
*
|
||||
|
|
|
|||
|
|
@ -643,6 +643,12 @@
|
|||
**/
|
||||
"enableDarkMode": "${ENABLE_DARK_MODE:true}",
|
||||
|
||||
/**
|
||||
* Enable creator-owned Pad-wide Settings and new-pad default seeding from My View.
|
||||
* Disabled by default to preserve the legacy single-settings behavior.
|
||||
**/
|
||||
"enablePadWideSettings": "${ENABLE_PAD_WIDE_SETTINGS:false}",
|
||||
|
||||
/*
|
||||
* From Etherpad 1.8.5 onwards, when Etherpad is in production mode commits from individual users are rate limited
|
||||
*
|
||||
|
|
|
|||
|
|
@ -83,13 +83,19 @@
|
|||
"pad.noCookie": "Cookie could not be found. Please allow cookies in your browser! Your session and settings will not be saved between visits. This may be due to Etherpad being included in an iFrame in some Browsers. Please ensure Etherpad is on the same subdomain/domain as the parent iFrame",
|
||||
"pad.permissionDenied": "You do not have permission to access this pad",
|
||||
|
||||
"pad.settings.padSettings": "Pad Settings",
|
||||
"pad.settings.title": "Settings",
|
||||
"pad.settings.padSettings": "Pad-wide Settings",
|
||||
"pad.settings.userSettings": "User Settings",
|
||||
"pad.settings.myView": "My View",
|
||||
"pad.settings.disablechat": "Disable Chat",
|
||||
"pad.settings.darkMode": "Dark mode",
|
||||
"pad.settings.stickychat": "Chat always on screen",
|
||||
"pad.settings.chatandusers": "Show Chat and Users",
|
||||
"pad.settings.colorcheck": "Authorship colors",
|
||||
"pad.settings.linenocheck": "Line numbers",
|
||||
"pad.settings.rtlcheck": "Read content from right to left?",
|
||||
"pad.settings.enforceSettings": "Enforce settings for other users",
|
||||
"pad.settings.enforcedNotice": "These settings are locked for you by this pad's creator. Ask the pad creator if you need them changed.",
|
||||
"pad.settings.fontType": "Font type:",
|
||||
"pad.settings.fontType.normal": "Normal",
|
||||
"pad.settings.language": "Language:",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,22 @@ import pad_utils from "../../static/js/pad_utils";
|
|||
import {SmartOpAssembler} from "../../static/js/SmartOpAssembler";
|
||||
import {timesLimit} from "async";
|
||||
|
||||
type PadViewSettings = {
|
||||
showAuthorColors: boolean;
|
||||
showLineNumbers: boolean;
|
||||
rtlIsTrue: boolean;
|
||||
padFontFamily: string;
|
||||
};
|
||||
|
||||
type PadSettings = {
|
||||
enforceSettings: boolean;
|
||||
showChat: boolean;
|
||||
alwaysShowChat: boolean;
|
||||
chatAndUsers: boolean;
|
||||
lang: string;
|
||||
view: PadViewSettings;
|
||||
};
|
||||
|
||||
/**
|
||||
* Copied from the Etherpad source code. It converts Windows line breaks to Unix
|
||||
* line breaks and convert Tabs to spaces
|
||||
|
|
@ -47,6 +63,7 @@ class Pad {
|
|||
private publicStatus: boolean;
|
||||
private id: string;
|
||||
private savedRevisions: any[];
|
||||
private padSettings: PadSettings;
|
||||
/**
|
||||
* @param id
|
||||
* @param [database] - Database object to access this pad's records (and only this pad's records;
|
||||
|
|
@ -64,6 +81,26 @@ class Pad {
|
|||
this.publicStatus = false;
|
||||
this.id = id;
|
||||
this.savedRevisions = [];
|
||||
this.padSettings = Pad.normalizePadSettings();
|
||||
}
|
||||
|
||||
static normalizePadSettings(rawPadSettings: any = {}): PadSettings {
|
||||
const rawView = rawPadSettings.view ?? {};
|
||||
return {
|
||||
enforceSettings: !!rawPadSettings.enforceSettings,
|
||||
showChat: rawPadSettings.showChat == null ? settings.padOptions.showChat !== false :
|
||||
!!rawPadSettings.showChat,
|
||||
alwaysShowChat: !!rawPadSettings.alwaysShowChat,
|
||||
chatAndUsers: !!rawPadSettings.chatAndUsers,
|
||||
lang: typeof rawPadSettings.lang === 'string' ? rawPadSettings.lang : 'en',
|
||||
view: {
|
||||
showAuthorColors: rawView.showAuthorColors == null ? true : !!rawView.showAuthorColors,
|
||||
showLineNumbers: rawView.showLineNumbers == null ?
|
||||
settings.padOptions.showLineNumbers !== false : !!rawView.showLineNumbers,
|
||||
rtlIsTrue: !!rawView.rtlIsTrue,
|
||||
padFontFamily: typeof rawView.padFontFamily === 'string' ? rawView.padFontFamily : '',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
apool() {
|
||||
|
|
@ -88,6 +125,22 @@ class Pad {
|
|||
return this.publicStatus;
|
||||
}
|
||||
|
||||
getPadSettings() {
|
||||
return Pad.normalizePadSettings(this.padSettings);
|
||||
}
|
||||
|
||||
setPadSettings(rawPadSettings: any) {
|
||||
const nextPadSettings = {
|
||||
...this.getPadSettings(),
|
||||
...rawPadSettings,
|
||||
view: {
|
||||
...this.getPadSettings().view,
|
||||
...(rawPadSettings?.view ?? {}),
|
||||
},
|
||||
};
|
||||
this.padSettings = Pad.normalizePadSettings(nextPadSettings);
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends a new revision
|
||||
* @param {Object} aChangeset The changeset to append to the pad
|
||||
|
|
@ -400,6 +453,7 @@ class Pad {
|
|||
const firstChangeset = makeSplice('\n', 0, 0, text, firstAttribs, this.pool);
|
||||
await this.appendRevision(firstChangeset, authorId);
|
||||
}
|
||||
this.padSettings = Pad.normalizePadSettings(this.padSettings);
|
||||
await hooks.aCallAll('padLoad', {pad: this});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ import {RateLimiterMemory} from 'rate-limiter-flexible';
|
|||
import {ChangesetRequest, PadUserInfo, SocketClientRequest} from "../types/SocketClientRequest";
|
||||
import {APool, AText, PadAuthor, PadType} from "../types/PadType";
|
||||
import {ChangeSet} from "../types/ChangeSet";
|
||||
import {ChatMessageMessage, ClientReadyMessage, ClientSaveRevisionMessage, ClientSuggestUserName, ClientUserChangesMessage, ClientVarMessage, CustomMessage, PadDeleteMessage, UserNewInfoMessage} from "../../static/js/types/SocketIOMessage";
|
||||
import {ChatMessageMessage, ClientReadyMessage, ClientSaveRevisionMessage, ClientSuggestUserName, ClientUserChangesMessage, ClientVarMessage, CustomMessage, PadDeleteMessage, PadOptionsMessage, UserNewInfoMessage} from "../../static/js/types/SocketIOMessage";
|
||||
import {Builder} from "../../static/js/Builder";
|
||||
const webaccess = require('../hooks/express/webaccess');
|
||||
const { checkValidRev } = require('../utils/checkValidRev');
|
||||
|
|
@ -263,6 +263,41 @@ const handlePadDelete = async (socket: any, padDeleteMessage: PadDeleteMessage)
|
|||
}
|
||||
}
|
||||
|
||||
const isPadCreator = async (pad: any, authorId: string) => authorId === await pad.getRevisionAuthor(0);
|
||||
|
||||
const handlePadOptionsMessage = async (
|
||||
socket: any, message: PadOptionsMessage & {data: {payload: PadOptionsMessage}}) => {
|
||||
const session = sessioninfos[socket.id];
|
||||
if (!session || !session.author || !session.padId) throw new Error('session not ready');
|
||||
if (!settings.enablePadWideSettings) return;
|
||||
if (!await padManager.doesPadExist(session.padId)) {
|
||||
messageLogger.warn(`Ignoring padoptions for missing pad ${session.padId}`);
|
||||
return;
|
||||
}
|
||||
const pad = await padManager.getPad(session.padId, null, session.author);
|
||||
if (!await isPadCreator(pad, session.author)) {
|
||||
socket.emit('shout', {
|
||||
type: 'COLLABROOM',
|
||||
data: {
|
||||
type: 'shoutMessage',
|
||||
payload: {
|
||||
message: {
|
||||
message: 'Only the pad creator can change pad settings',
|
||||
sticky: false,
|
||||
},
|
||||
timestamp: Date.now(),
|
||||
},
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
pad.setPadSettings(message.data.payload.options);
|
||||
await pad.saveToDatabase();
|
||||
_getRoomSockets(session.padId).forEach((socket) => {
|
||||
socket.emit('message', message);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Handles a message from a user
|
||||
|
|
@ -413,6 +448,11 @@ exports.handleMessage = async (socket:any, message: ClientVarMessage) => {
|
|||
try {
|
||||
switch (type) {
|
||||
case 'suggestUserName': handleSuggestUserName(socket, message as unknown as ClientSuggestUserName); break;
|
||||
case 'padoptions':
|
||||
await handlePadOptionsMessage(
|
||||
socket,
|
||||
message as unknown as PadOptionsMessage & {data: {payload: PadOptionsMessage}});
|
||||
break;
|
||||
default: throw new Error('unknown message type');
|
||||
}
|
||||
} catch (err) {
|
||||
|
|
@ -883,8 +923,13 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => {
|
|||
]);
|
||||
({colorId: authorColorId, name: authorName} = await authorManager.getAuthor(sessionInfo.author));
|
||||
|
||||
const padExisted = await padManager.doesPadExist(sessionInfo.padId);
|
||||
// load the pad-object from the database
|
||||
const pad = await padManager.getPad(sessionInfo.padId, null, sessionInfo.author);
|
||||
if (settings.enablePadWideSettings && !padExisted && message.padSettingsDefaults) {
|
||||
pad.setPadSettings(message.padSettingsDefaults);
|
||||
await pad.saveToDatabase();
|
||||
}
|
||||
|
||||
// these db requests all need the pad object (timestamp of latest revision, author data)
|
||||
const authors = pad.getAllAuthors();
|
||||
|
|
@ -1025,6 +1070,8 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => {
|
|||
|
||||
// Warning: never ever send sessionInfo.padId to the client. If the client is read only you
|
||||
// would open a security hole 1 swedish mile wide...
|
||||
const canEditPadSettings = settings.enablePadWideSettings &&
|
||||
!sessionInfo.readonly && await isPadCreator(pad, sessionInfo.author);
|
||||
const clientVars:MapArrayType<any> = {
|
||||
skinName: settings.skinName,
|
||||
skinVariants: settings.skinVariants,
|
||||
|
|
@ -1033,9 +1080,10 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => {
|
|||
maxRevisions: 100,
|
||||
},
|
||||
enableDarkMode: settings.enableDarkMode,
|
||||
enablePadWideSettings: settings.enablePadWideSettings,
|
||||
automaticReconnectionTimeout: settings.automaticReconnectionTimeout,
|
||||
initialRevisionList: [],
|
||||
initialOptions: {},
|
||||
initialOptions: pad.getPadSettings(),
|
||||
savedRevisions: pad.getSavedRevisions(),
|
||||
collab_client_vars: {
|
||||
initialAttributedText: atext,
|
||||
|
|
@ -1060,6 +1108,7 @@ const handleClientReady = async (socket:any, message: ClientReadyMessage) => {
|
|||
numConnectedUsers: roomSockets.length + 1, // +1 for this user (not yet in room)
|
||||
readOnlyId: sessionInfo.readOnlyPadId,
|
||||
readonly: sessionInfo.readonly,
|
||||
canEditPadSettings,
|
||||
serverTimestamp: Date.now(),
|
||||
sessionRefreshInterval: settings.cookie.sessionRefreshInterval,
|
||||
userId: sessionInfo.author,
|
||||
|
|
|
|||
|
|
@ -172,6 +172,7 @@ export type SettingsType = {
|
|||
},
|
||||
updateServer: string,
|
||||
enableDarkMode: boolean,
|
||||
enablePadWideSettings: boolean,
|
||||
skinName: string | null,
|
||||
skinVariants: string,
|
||||
ip: string,
|
||||
|
|
@ -294,7 +295,7 @@ export type SettingsType = {
|
|||
lowerCasePadIds: boolean,
|
||||
randomVersionString: string,
|
||||
gitVersion: string
|
||||
getPublicSettings: () => Pick<SettingsType, "title" | "skinVariants"|"randomVersionString"|"skinName"|"toolbar"| "exposeVersion"| "gitVersion">,
|
||||
getPublicSettings: () => Pick<SettingsType, "title" | "skinVariants"|"randomVersionString"|"skinName"|"toolbar"| "exposeVersion"| "gitVersion" | "enablePadWideSettings">,
|
||||
}
|
||||
|
||||
const settings: SettingsType = {
|
||||
|
|
@ -328,6 +329,7 @@ const settings: SettingsType = {
|
|||
},
|
||||
updateServer: "https://static.etherpad.org",
|
||||
enableDarkMode: true,
|
||||
enablePadWideSettings: false,
|
||||
/*
|
||||
* Skin name.
|
||||
*
|
||||
|
|
@ -657,6 +659,7 @@ const settings: SettingsType = {
|
|||
title: settings.title,
|
||||
skinName: settings.skinName,
|
||||
skinVariants: settings.skinVariants,
|
||||
enablePadWideSettings: settings.enablePadWideSettings,
|
||||
}
|
||||
},
|
||||
gitVersion: getGitCommit(),
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ exports.chat = (() => {
|
|||
let chatMentions = 0;
|
||||
return {
|
||||
show() {
|
||||
if (pad.settings.hideChat) return;
|
||||
$('#chaticon').removeClass('visible');
|
||||
$('#chatbox').addClass('visible');
|
||||
this.scrollDown(true);
|
||||
|
|
@ -49,7 +50,7 @@ exports.chat = (() => {
|
|||
}, 100);
|
||||
},
|
||||
// Make chat stick to right hand side of screen
|
||||
stickToScreen(fromInitialCall) {
|
||||
stickToScreen(fromInitialCall, persistPreference = true) {
|
||||
if ($('#options-stickychat').prop('checked')) {
|
||||
$('#options-stickychat').prop('checked', false);
|
||||
}
|
||||
|
|
@ -65,13 +66,13 @@ exports.chat = (() => {
|
|||
$('#chatbox').css('display', 'flex');
|
||||
}, 0);
|
||||
|
||||
padcookie.setPref('chatAlwaysVisible', isStuck);
|
||||
if (persistPreference) padcookie.setPref('chatAlwaysVisible', isStuck);
|
||||
$('#options-stickychat').prop('checked', isStuck);
|
||||
},
|
||||
chatAndUsers(fromInitialCall) {
|
||||
chatAndUsers(fromInitialCall, persistPreference = true) {
|
||||
const toEnable = $('#options-chatandusers').is(':checked');
|
||||
if (toEnable || !userAndChat || fromInitialCall) {
|
||||
this.stickToScreen(true);
|
||||
this.stickToScreen(true, persistPreference);
|
||||
$('#options-stickychat').prop('checked', true);
|
||||
$('#options-chatandusers').prop('checked', true);
|
||||
$('#options-stickychat').prop('disabled', true);
|
||||
|
|
@ -80,7 +81,7 @@ exports.chat = (() => {
|
|||
$('#options-stickychat').prop('disabled', false);
|
||||
userAndChat = false;
|
||||
}
|
||||
padcookie.setPref('chatAndUsers', userAndChat);
|
||||
if (persistPreference) padcookie.setPref('chatAndUsers', userAndChat);
|
||||
$('#users, .sticky-container')
|
||||
.toggleClass('chatAndUsers popup-show stickyUsers', userAndChat);
|
||||
$('#chatbox').toggleClass('chatAndUsersChat', userAndChat);
|
||||
|
|
@ -204,7 +205,7 @@ exports.chat = (() => {
|
|||
count++;
|
||||
$('#chatcounter').text(count);
|
||||
|
||||
if (!chatOpen && ctx.duration > 0) {
|
||||
if (!pad.settings.hideChat && !chatOpen && ctx.duration > 0) {
|
||||
const text = $('<p>')
|
||||
.append($('<span>').addClass('author-name').text(ctx.authorName))
|
||||
// ctx.text was HTML-escaped before calling the hook. Hook functions are trusted
|
||||
|
|
|
|||
|
|
@ -80,10 +80,13 @@ const getParameters = [
|
|||
name: 'showChat',
|
||||
checkVal: null,
|
||||
callback: (val) => {
|
||||
clientVars.initialOptions.showChat = val !== 'false';
|
||||
if (val === 'false') {
|
||||
settings.hideChat = true;
|
||||
chat.hide();
|
||||
$('#chaticon').hide();
|
||||
} else {
|
||||
settings.hideChat = false;
|
||||
}
|
||||
},
|
||||
},
|
||||
|
|
@ -176,6 +179,43 @@ const getParams = () => {
|
|||
|
||||
const getUrlVars = () => new URL(window.location.href).searchParams;
|
||||
|
||||
const getCookieLanguage = () => {
|
||||
const cp = (window as any).clientVars?.cookiePrefix || '';
|
||||
return Cookies.get(`${cp}language`) || Cookies.get('language');
|
||||
};
|
||||
|
||||
const getMyViewOverrides = () => {
|
||||
const language = getCookieLanguage();
|
||||
const overrides = {
|
||||
showChat: padcookie.getPref('showChat'),
|
||||
alwaysShowChat: padcookie.getPref('chatAlwaysVisible'),
|
||||
chatAndUsers: padcookie.getPref('chatAndUsers'),
|
||||
lang: language,
|
||||
view: {
|
||||
showAuthorColors: padcookie.getPref('showAuthorshipColors'),
|
||||
showLineNumbers: padcookie.getPref('showLineNumbers'),
|
||||
rtlIsTrue: padcookie.getPref('rtlIsTrue'),
|
||||
padFontFamily: padcookie.getPref('padFontFamily'),
|
||||
},
|
||||
};
|
||||
if (language == null) delete overrides.lang;
|
||||
return overrides;
|
||||
};
|
||||
|
||||
const normalizeChatOptions = (options) => {
|
||||
if (options.showChat === false) {
|
||||
options.alwaysShowChat = false;
|
||||
options.chatAndUsers = false;
|
||||
}
|
||||
if (options.chatAndUsers === true) {
|
||||
options.showChat = true;
|
||||
options.alwaysShowChat = true;
|
||||
} else if (options.alwaysShowChat === true) {
|
||||
options.showChat = true;
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
const sendClientReady = (isReconnect) => {
|
||||
let padId = document.location.pathname.substring(document.location.pathname.lastIndexOf('/') + 1);
|
||||
// unescape necessary due to Safari and Opera interpretation of spaces
|
||||
|
|
@ -204,7 +244,7 @@ const sendClientReady = (isReconnect) => {
|
|||
name: params.get('userName'),
|
||||
};
|
||||
|
||||
const msg = {
|
||||
const msg: any = {
|
||||
component: 'pad',
|
||||
type: 'CLIENT_READY',
|
||||
padId,
|
||||
|
|
@ -212,6 +252,16 @@ const sendClientReady = (isReconnect) => {
|
|||
token,
|
||||
userInfo,
|
||||
};
|
||||
const overrides = getMyViewOverrides();
|
||||
const viewOverrides = Object.fromEntries(
|
||||
Object.entries(overrides.view || {}).filter(([, v]) => v != null));
|
||||
const hasTopLevelOverrides = ['showChat', 'alwaysShowChat', 'chatAndUsers', 'lang']
|
||||
.some((k) => overrides[k] != null);
|
||||
if (Object.keys(viewOverrides).length > 0 || hasTopLevelOverrides) {
|
||||
if (Object.keys(viewOverrides).length > 0) overrides.view = viewOverrides;
|
||||
else delete overrides.view;
|
||||
msg.padSettingsDefaults = overrides;
|
||||
}
|
||||
|
||||
// this is a reconnect, lets tell the server our revisionnumber
|
||||
if (isReconnect) {
|
||||
|
|
@ -405,12 +455,123 @@ const pad = {
|
|||
getClientIp: () => clientVars.clientIp,
|
||||
getColorPalette: () => clientVars.colorPalette,
|
||||
getPrivilege: (name) => clientVars.accountPrivs[name],
|
||||
canEditPadSettings: () => !!clientVars.canEditPadSettings,
|
||||
getUserId: () => pad.myUserInfo.userId,
|
||||
getUserName: () => pad.myUserInfo.name,
|
||||
userList: () => paduserlist.users(),
|
||||
isPadSettingsEnforcedForMe: () => !!pad.padOptions.enforceSettings && !pad.canEditPadSettings(),
|
||||
sendClientMessage: (msg) => {
|
||||
pad.collabClient.sendClientMessage(msg);
|
||||
},
|
||||
getEffectivePadOptions: () => {
|
||||
const effectiveOptions = $.extend(true, {}, pad.padOptions);
|
||||
if (pad.isPadSettingsEnforcedForMe()) return normalizeChatOptions(effectiveOptions);
|
||||
const overrides = getMyViewOverrides();
|
||||
for (const key of ['showChat', 'alwaysShowChat', 'chatAndUsers', 'lang']) {
|
||||
if (overrides[key] != null) effectiveOptions[key] = overrides[key];
|
||||
}
|
||||
if (!effectiveOptions.view) effectiveOptions.view = {};
|
||||
for (const [key, value] of Object.entries(overrides.view)) {
|
||||
if (value != null) effectiveOptions.view[key] = value;
|
||||
}
|
||||
return normalizeChatOptions(effectiveOptions);
|
||||
},
|
||||
refreshPadSettingsControls: () => {
|
||||
const padOptions = normalizeChatOptions($.extend(true, {}, pad.padOptions || {}));
|
||||
const view = padOptions.view || {};
|
||||
$('#padsettings-options-disablechat').prop('checked', padOptions.showChat === false);
|
||||
$('#padsettings-options-stickychat').prop('checked', !!padOptions.alwaysShowChat);
|
||||
$('#padsettings-options-chatandusers').prop('checked', !!padOptions.chatAndUsers);
|
||||
$('#padsettings-options-colorscheck').prop('checked', view.showAuthorColors !== false);
|
||||
$('#padsettings-options-linenoscheck').prop('checked', view.showLineNumbers !== false);
|
||||
$('#padsettings-options-rtlcheck').prop('checked', !!view.rtlIsTrue);
|
||||
$('#padsettings-viewfontmenu').val(view.padFontFamily || '');
|
||||
$('#padsettings-languagemenu').val(padOptions.lang || 'en');
|
||||
$('#padsettings-enforcecheck').prop('checked', !!padOptions.enforceSettings);
|
||||
$('#padsettings-options-stickychat, #padsettings-options-chatandusers')
|
||||
.prop('disabled', padOptions.showChat === false);
|
||||
if ($('select').niceSelect) $('select').niceSelect('update');
|
||||
},
|
||||
refreshMyViewControls: () => {
|
||||
const effectiveOptions = pad.getEffectivePadOptions();
|
||||
const disabled = pad.isPadSettingsEnforcedForMe();
|
||||
$('#options-disablechat').prop('checked', effectiveOptions.showChat === false);
|
||||
$('#options-stickychat').prop('checked', !!effectiveOptions.alwaysShowChat);
|
||||
$('#options-chatandusers').prop('checked', !!effectiveOptions.chatAndUsers);
|
||||
$('#options-colorscheck').prop('checked', effectiveOptions.view?.showAuthorColors !== false);
|
||||
$('#options-linenoscheck').prop('checked', effectiveOptions.view?.showLineNumbers !== false);
|
||||
$('#options-rtlcheck').prop('checked', !!effectiveOptions.view?.rtlIsTrue);
|
||||
$('#viewfontmenu').val(effectiveOptions.view?.padFontFamily || '');
|
||||
$('#languagemenu').val(effectiveOptions.lang || 'en');
|
||||
$('#settings input[id^="options-"]').prop('disabled', disabled);
|
||||
$('#viewfontmenu, #languagemenu').prop('disabled', disabled);
|
||||
$('#options-stickychat, #options-chatandusers')
|
||||
.prop('disabled', disabled || effectiveOptions.showChat === false);
|
||||
$('#enforce-settings-notice').prop('hidden', !disabled);
|
||||
if ($('select').niceSelect) $('select').niceSelect('update');
|
||||
},
|
||||
setMyViewOption: (key, value) => {
|
||||
switch (key) {
|
||||
case 'showChat':
|
||||
padcookie.setPref('showChat', value);
|
||||
if (!value) {
|
||||
padcookie.setPref('chatAlwaysVisible', false);
|
||||
padcookie.setPref('chatAndUsers', false);
|
||||
}
|
||||
break;
|
||||
case 'alwaysShowChat':
|
||||
padcookie.setPref('chatAlwaysVisible', value);
|
||||
if (value) padcookie.setPref('showChat', true);
|
||||
break;
|
||||
case 'chatAndUsers':
|
||||
padcookie.setPref('chatAndUsers', value);
|
||||
if (value) padcookie.setPref('chatAlwaysVisible', true);
|
||||
if (value) padcookie.setPref('showChat', true);
|
||||
break;
|
||||
case 'showAuthorColors':
|
||||
padcookie.setPref('showAuthorshipColors', value);
|
||||
break;
|
||||
default:
|
||||
padcookie.setPref(key, value);
|
||||
break;
|
||||
}
|
||||
pad.refreshMyViewControls();
|
||||
pad.applyOptionsChange();
|
||||
},
|
||||
setMyViewLanguage: (lang) => {
|
||||
const cp = (window as any).clientVars?.cookiePrefix || '';
|
||||
Cookies.set(`${cp}language`, lang);
|
||||
pad.refreshMyViewControls();
|
||||
pad.applyOptionsChange();
|
||||
},
|
||||
applyShowChat: (enabled) => {
|
||||
settings.hideChat = !enabled;
|
||||
if (enabled) {
|
||||
if (!window.clientVars.readonly) $('#chaticon').show();
|
||||
} else {
|
||||
$('#users, .sticky-container').removeClass('chatAndUsers popup-show stickyUsers');
|
||||
$('#chatbox').removeClass('chatAndUsersChat stickyChat visible').hide();
|
||||
$('#options-stickychat, #options-chatandusers').prop('checked', false);
|
||||
$('#chaticon').hide();
|
||||
}
|
||||
},
|
||||
applyStickyChat: (enabled) => {
|
||||
const isSticky = $('#chatbox').hasClass('stickyChat');
|
||||
$('#options-stickychat').prop('checked', enabled);
|
||||
if (enabled !== isSticky) chat.stickToScreen(enabled, false);
|
||||
if (!enabled) $('#options-stickychat').prop('disabled', false);
|
||||
},
|
||||
applyChatAndUsers: (enabled) => {
|
||||
const isEnabled = $('#users').hasClass('chatAndUsers');
|
||||
$('#options-chatandusers').prop('checked', enabled);
|
||||
if (enabled !== isEnabled) chat.chatAndUsers(enabled, false);
|
||||
if (!enabled) $('#options-stickychat').prop('disabled', false);
|
||||
},
|
||||
applyLanguage: (lang) => {
|
||||
html10n.localize([lang, 'en']);
|
||||
$('#languagemenu').val(lang);
|
||||
if ($('select').niceSelect) $('select').niceSelect('update');
|
||||
},
|
||||
|
||||
init() {
|
||||
padutils.setupGlobalExceptionHandler();
|
||||
|
|
@ -449,32 +610,13 @@ const pad = {
|
|||
setTimeout(() => {
|
||||
padeditor.ace.focus();
|
||||
}, 0);
|
||||
const optionsStickyChat = $('#options-stickychat');
|
||||
optionsStickyChat.on('click', () => { chat.stickToScreen(); });
|
||||
// if we have a cookie for always showing chat then show it
|
||||
if (padcookie.getPref('chatAlwaysVisible')) {
|
||||
chat.stickToScreen(true); // stick it to the screen
|
||||
optionsStickyChat.prop('checked', true); // set the checkbox to on
|
||||
}
|
||||
// if we have a cookie for always showing chat then show it
|
||||
if (padcookie.getPref('chatAndUsers')) {
|
||||
chat.chatAndUsers(true); // stick it to the screen
|
||||
$('#options-chatandusers').prop('checked', true); // set the checkbox to on
|
||||
}
|
||||
if (padcookie.getPref('showAuthorshipColors') === false) {
|
||||
pad.changeViewOption('showAuthorColors', false);
|
||||
}
|
||||
if (padcookie.getPref('showLineNumbers') === false) {
|
||||
pad.changeViewOption('showLineNumbers', false);
|
||||
}
|
||||
pad.refreshPadSettingsControls();
|
||||
pad.applyOptionsChange();
|
||||
pad.refreshMyViewControls();
|
||||
if (settings.rtlIsExplicit) {
|
||||
// URL or server config explicitly set RTL — takes priority over cookie
|
||||
pad.changeViewOption('rtlIsTrue', settings.rtlIsTrue === true);
|
||||
} else if (padcookie.getPref('rtlIsTrue') === true) {
|
||||
pad.changeViewOption('rtlIsTrue', true);
|
||||
}
|
||||
pad.changeViewOption('padFontFamily', padcookie.getPref('padFontFamily'));
|
||||
$('#viewfontmenu').val(padcookie.getPref('padFontFamily')).niceSelect('update');
|
||||
|
||||
// Prevent sticky chat or chat and users to be checked for mobiles
|
||||
const checkChatAndUsersVisibility = (x) => {
|
||||
|
|
@ -489,13 +631,13 @@ const pad = {
|
|||
|
||||
$('#editorcontainer').addClass('initialized');
|
||||
|
||||
if (window.clientVars.enableDarkMode) {
|
||||
$('#theme-switcher').attr('style', 'display: flex;');
|
||||
}
|
||||
|
||||
if (window.location.hash.toLowerCase() !== '#skinvariantsbuilder' && window.clientVars.enableDarkMode && (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) && !skinVariants.isWhiteModeEnabledInLocalStorage()) {
|
||||
skinVariants.updateSkinVariantsClasses(['super-dark-editor', 'dark-background', 'super-dark-toolbar']);
|
||||
}
|
||||
if (window.clientVars.enableDarkMode) {
|
||||
$('#theme-toggle-row').prop('hidden', false);
|
||||
$('#options-darkmode').prop('checked', skinVariants.isDarkMode());
|
||||
}
|
||||
|
||||
hooks.aCallAll('postAceInit', {ace: padeditor.ace, clientVars, pad});
|
||||
};
|
||||
|
|
@ -503,7 +645,7 @@ const pad = {
|
|||
// order of inits is important here:
|
||||
padimpexp.init(this);
|
||||
padsavedrevs.init(this);
|
||||
padeditor.init(pad.padOptions.view || {}, this).then(postAceInit);
|
||||
padeditor.init(pad.getEffectivePadOptions().view || {}, this).then(postAceInit);
|
||||
paduserlist.init(pad.myUserInfo, this);
|
||||
padconnectionstatus.init();
|
||||
padmodals.init(this);
|
||||
|
|
@ -592,7 +734,20 @@ const pad = {
|
|||
changePadOption: (key, value) => {
|
||||
const options = {};
|
||||
options[key] = value;
|
||||
pad.handleOptionsChange(options);
|
||||
pad.applyPadSettings(options);
|
||||
pad.collabClient.sendClientMessage(
|
||||
{
|
||||
type: 'padoptions',
|
||||
options,
|
||||
changedBy: pad.myUserInfo.name || 'unnamed',
|
||||
});
|
||||
},
|
||||
changePadViewOption: (key, value) => {
|
||||
const options = {
|
||||
view: {},
|
||||
};
|
||||
options.view[key] = value;
|
||||
pad.applyPadSettings(options);
|
||||
pad.collabClient.sendClientMessage(
|
||||
{
|
||||
type: 'padoptions',
|
||||
|
|
@ -601,25 +756,43 @@ const pad = {
|
|||
});
|
||||
},
|
||||
changeViewOption: (key, value) => {
|
||||
const options = {
|
||||
view: {},
|
||||
};
|
||||
options.view[key] = value;
|
||||
pad.handleOptionsChange(options);
|
||||
const effectiveOptions = pad.getEffectivePadOptions();
|
||||
if (!effectiveOptions.view) effectiveOptions.view = {};
|
||||
effectiveOptions.view[key] = value;
|
||||
padeditor.setViewOptions(effectiveOptions.view);
|
||||
},
|
||||
handleOptionsChange: (opts) => {
|
||||
applyPadSettings: (opts = {}) => {
|
||||
// opts object is a full set of options or just
|
||||
// some options to change
|
||||
for (const key of ['enforceSettings', 'showChat', 'alwaysShowChat', 'chatAndUsers', 'lang']) {
|
||||
if (opts[key] == null) continue;
|
||||
pad.padOptions[key] = key === 'lang' ? opts[key] : `${opts[key]}` === 'true';
|
||||
}
|
||||
if (opts.view) {
|
||||
if (!pad.padOptions.view) {
|
||||
pad.padOptions.view = {};
|
||||
}
|
||||
for (const [k, v] of Object.entries(opts.view)) {
|
||||
pad.padOptions.view[k] = v;
|
||||
padcookie.setPref(k, v);
|
||||
}
|
||||
padeditor.setViewOptions(pad.padOptions.view);
|
||||
}
|
||||
normalizeChatOptions(pad.padOptions);
|
||||
pad.refreshPadSettingsControls();
|
||||
pad.applyOptionsChange();
|
||||
},
|
||||
applyOptionsChange: () => {
|
||||
const effectiveOptions = pad.getEffectivePadOptions();
|
||||
padeditor.setViewOptions(effectiveOptions.view || {});
|
||||
pad.applyShowChat(effectiveOptions.showChat !== false);
|
||||
if (effectiveOptions.showChat !== false) {
|
||||
if (effectiveOptions.lang) pad.applyLanguage(effectiveOptions.lang);
|
||||
pad.applyChatAndUsers(!!effectiveOptions.chatAndUsers);
|
||||
if (!effectiveOptions.chatAndUsers) pad.applyStickyChat(!!effectiveOptions.alwaysShowChat);
|
||||
}
|
||||
pad.refreshMyViewControls();
|
||||
},
|
||||
handleOptionsChange: (opts) => {
|
||||
pad.applyPadSettings(opts);
|
||||
},
|
||||
// caller shouldn't mutate the object
|
||||
getPadOptions: () => pad.padOptions,
|
||||
|
|
@ -708,39 +881,19 @@ const pad = {
|
|||
}
|
||||
},
|
||||
handleIsFullyConnected: (isConnected, isInitialConnect) => {
|
||||
pad.determineChatVisibility(isConnected && !isInitialConnect);
|
||||
pad.determineChatAndUsersVisibility(isConnected && !isInitialConnect);
|
||||
pad.determineAuthorshipColorsVisibility();
|
||||
pad.refreshMyViewControls();
|
||||
setTimeout(() => {
|
||||
padeditbar.toggleDropDown('none');
|
||||
}, 1000);
|
||||
},
|
||||
determineChatVisibility: (asNowConnectedFeedback) => {
|
||||
const chatVisCookie = padcookie.getPref('chatAlwaysVisible');
|
||||
if (chatVisCookie) { // if the cookie is set for chat always visible
|
||||
chat.stickToScreen(true); // stick it to the screen
|
||||
$('#options-stickychat').prop('checked', true); // set the checkbox to on
|
||||
} else {
|
||||
$('#options-stickychat').prop('checked', false); // set the checkbox for off
|
||||
}
|
||||
pad.refreshMyViewControls();
|
||||
},
|
||||
determineChatAndUsersVisibility: (asNowConnectedFeedback) => {
|
||||
const chatAUVisCookie = padcookie.getPref('chatAndUsersVisible');
|
||||
if (chatAUVisCookie) { // if the cookie is set for chat always visible
|
||||
chat.chatAndUsers(true); // stick it to the screen
|
||||
$('#options-chatandusers').prop('checked', true); // set the checkbox to on
|
||||
} else {
|
||||
$('#options-chatandusers').prop('checked', false); // set the checkbox for off
|
||||
}
|
||||
pad.refreshMyViewControls();
|
||||
},
|
||||
determineAuthorshipColorsVisibility: () => {
|
||||
const authColCookie = padcookie.getPref('showAuthorshipColors');
|
||||
if (authColCookie) {
|
||||
pad.changeViewOption('showAuthorColors', true);
|
||||
$('#options-colorscheck').prop('checked', true);
|
||||
} else {
|
||||
$('#options-colorscheck').prop('checked', false);
|
||||
}
|
||||
pad.refreshMyViewControls();
|
||||
},
|
||||
handleCollabAction: (action) => {
|
||||
if (action === 'commitPerformed') {
|
||||
|
|
|
|||
|
|
@ -22,8 +22,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import padutils,{Cookies} from "./pad_utils";
|
||||
const padcookie = require('./pad_cookie').padcookie;
|
||||
import padutils from "./pad_utils";
|
||||
const Ace2Editor = require('./ace').Ace2Editor;
|
||||
import html10n from '../js/vendors/html10n'
|
||||
const skinVariants = require('./skin_variants');
|
||||
|
|
@ -56,34 +55,86 @@ const padeditor = (() => {
|
|||
$('#viewbarcontents').show();
|
||||
},
|
||||
initViewOptions: () => {
|
||||
// Line numbers
|
||||
// My View
|
||||
padutils.bindCheckboxChange($('#options-disablechat'), () => {
|
||||
pad.setMyViewOption('showChat', !padutils.getCheckbox($('#options-disablechat')));
|
||||
});
|
||||
padutils.bindCheckboxChange($('#options-stickychat'), () => {
|
||||
pad.setMyViewOption('alwaysShowChat', padutils.getCheckbox($('#options-stickychat')));
|
||||
});
|
||||
padutils.bindCheckboxChange($('#options-chatandusers'), () => {
|
||||
pad.setMyViewOption('chatAndUsers', padutils.getCheckbox($('#options-chatandusers')));
|
||||
});
|
||||
padutils.bindCheckboxChange($('#options-colorscheck'), () => {
|
||||
pad.setMyViewOption('showAuthorColors', padutils.getCheckbox($('#options-colorscheck')));
|
||||
});
|
||||
padutils.bindCheckboxChange($('#options-linenoscheck'), () => {
|
||||
pad.changeViewOption('showLineNumbers', padutils.getCheckbox($('#options-linenoscheck')));
|
||||
pad.setMyViewOption('showLineNumbers', padutils.getCheckbox($('#options-linenoscheck')));
|
||||
});
|
||||
padutils.bindCheckboxChange($('#options-rtlcheck'), () => {
|
||||
pad.setMyViewOption('rtlIsTrue', padutils.getCheckbox($('#options-rtlcheck')));
|
||||
});
|
||||
$('#viewfontmenu').on('change', () => {
|
||||
pad.setMyViewOption('padFontFamily', $('#viewfontmenu').val());
|
||||
});
|
||||
$('#languagemenu').on('change', () => {
|
||||
pad.setMyViewLanguage($('#languagemenu').val());
|
||||
});
|
||||
|
||||
// Pad settings
|
||||
padutils.bindCheckboxChange($('#padsettings-enforcecheck'), () => {
|
||||
pad.changePadOption('enforceSettings', padutils.getCheckbox($('#padsettings-enforcecheck')));
|
||||
});
|
||||
padutils.bindCheckboxChange($('#padsettings-options-disablechat'), () => {
|
||||
pad.changePadOption('showChat', !padutils.getCheckbox($('#padsettings-options-disablechat')));
|
||||
});
|
||||
padutils.bindCheckboxChange($('#padsettings-options-stickychat'), () => {
|
||||
pad.changePadOption(
|
||||
'alwaysShowChat', padutils.getCheckbox($('#padsettings-options-stickychat')));
|
||||
});
|
||||
padutils.bindCheckboxChange($('#padsettings-options-chatandusers'), () => {
|
||||
pad.changePadOption(
|
||||
'chatAndUsers', padutils.getCheckbox($('#padsettings-options-chatandusers')));
|
||||
});
|
||||
// Line numbers
|
||||
padutils.bindCheckboxChange($('#padsettings-options-linenoscheck'), () => {
|
||||
pad.changePadViewOption(
|
||||
'showLineNumbers', padutils.getCheckbox($('#padsettings-options-linenoscheck')));
|
||||
});
|
||||
|
||||
// Author colors
|
||||
padutils.bindCheckboxChange($('#options-colorscheck'), () => {
|
||||
padcookie.setPref('showAuthorshipColors', padutils.getCheckbox('#options-colorscheck'));
|
||||
pad.changeViewOption('showAuthorColors', padutils.getCheckbox('#options-colorscheck'));
|
||||
padutils.bindCheckboxChange($('#padsettings-options-colorscheck'), () => {
|
||||
pad.changePadViewOption(
|
||||
'showAuthorColors', padutils.getCheckbox('#padsettings-options-colorscheck'));
|
||||
});
|
||||
|
||||
// Right to left
|
||||
padutils.bindCheckboxChange($('#options-rtlcheck'), () => {
|
||||
pad.changeViewOption('rtlIsTrue', padutils.getCheckbox($('#options-rtlcheck')));
|
||||
padutils.bindCheckboxChange($('#padsettings-options-rtlcheck'), () => {
|
||||
pad.changePadViewOption(
|
||||
'rtlIsTrue', padutils.getCheckbox($('#padsettings-options-rtlcheck')));
|
||||
});
|
||||
html10n.bind('localized', () => {
|
||||
// Don't override RTL when explicitly set via URL/server or user cookie
|
||||
if (settings && settings.rtlIsExplicit) return;
|
||||
if (padcookie.getPref('rtlIsTrue') === true) return;
|
||||
pad.changeViewOption('rtlIsTrue', ('rtl' === html10n.getDirection()));
|
||||
padutils.setCheckbox($('#options-rtlcheck'), ('rtl' === html10n.getDirection()));
|
||||
$('#languagemenu').val(html10n.getLanguage());
|
||||
$('#padsettings-languagemenu').val(html10n.getLanguage());
|
||||
});
|
||||
|
||||
|
||||
|
||||
// font family change
|
||||
$('#viewfontmenu').on('change', () => {
|
||||
pad.changeViewOption('padFontFamily', $('#viewfontmenu').val());
|
||||
$('#padsettings-viewfontmenu').on('change', () => {
|
||||
pad.changePadViewOption('padFontFamily', $('#padsettings-viewfontmenu').val());
|
||||
});
|
||||
|
||||
padutils.bindCheckboxChange($('#options-darkmode'), () => {
|
||||
const isDark = padutils.getCheckbox($('#options-darkmode'));
|
||||
skinVariants.setDarkModeInLocalStorage(isDark);
|
||||
if (isDark) {
|
||||
skinVariants.updateSkinVariantsClasses(
|
||||
['super-dark-editor', 'dark-background', 'super-dark-toolbar']);
|
||||
} else {
|
||||
skinVariants.updateSkinVariantsClasses(
|
||||
['super-light-toolbar super-light-editor light-background']);
|
||||
}
|
||||
});
|
||||
|
||||
// delete pad
|
||||
|
|
@ -116,20 +167,8 @@ const padeditor = (() => {
|
|||
}
|
||||
})
|
||||
|
||||
// theme switch
|
||||
$('#theme-switcher').on('click',()=>{
|
||||
if (skinVariants.isDarkMode()) {
|
||||
skinVariants.setDarkModeInLocalStorage(false);
|
||||
skinVariants.updateSkinVariantsClasses(['super-light-toolbar super-light-editor light-background']);
|
||||
} else {
|
||||
skinVariants.setDarkModeInLocalStorage(true);
|
||||
skinVariants.updateSkinVariantsClasses(['super-dark-editor', 'dark-background', 'super-dark-toolbar']);
|
||||
}
|
||||
})
|
||||
|
||||
// Language
|
||||
html10n.bind('localized', () => {
|
||||
$('#languagemenu').val(html10n.getLanguage());
|
||||
// translate the value of 'unnamed' and 'Enter your name' textboxes in the userlist
|
||||
|
||||
// this does not interfere with html10n's normal value-setting because
|
||||
|
|
@ -143,15 +182,14 @@ const padeditor = (() => {
|
|||
}
|
||||
});
|
||||
});
|
||||
$('#languagemenu').val(html10n.getLanguage());
|
||||
$('#languagemenu').on('change', () => {
|
||||
const cp = (window as any).clientVars?.cookiePrefix || '';
|
||||
Cookies.set(`${cp}language`, $('#languagemenu').val());
|
||||
html10n.localize([$('#languagemenu').val(), 'en']);
|
||||
if ($('select').niceSelect) {
|
||||
$('select').niceSelect('update');
|
||||
}
|
||||
$('#padsettings-languagemenu').val(html10n.getLanguage());
|
||||
$('#padsettings-languagemenu').on('change', () => {
|
||||
pad.changePadOption('lang', $('#padsettings-languagemenu').val());
|
||||
});
|
||||
if (pad.canEditPadSettings()) {
|
||||
$('#pad-settings-section').prop('hidden', false);
|
||||
$('.settings-sections').addClass('has-pad-settings');
|
||||
}
|
||||
},
|
||||
setViewOptions: (newOptions) => {
|
||||
const getOption = (key, defaultValue) => {
|
||||
|
|
@ -183,6 +221,8 @@ const padeditor = (() => {
|
|||
}
|
||||
|
||||
self.ace.setProperty('textface', newOptions.padFontFamily || '');
|
||||
$('#viewfontmenu').val(newOptions.padFontFamily || '');
|
||||
if ($('select').niceSelect) $('select').niceSelect('update');
|
||||
},
|
||||
dispose: () => {
|
||||
if (self.ace) {
|
||||
|
|
|
|||
|
|
@ -73,8 +73,10 @@ export type ClientVarPayload = {
|
|||
chatHead: number,
|
||||
readonly: boolean,
|
||||
serverTimestamp: number,
|
||||
initialOptions: MapArrayType<string>,
|
||||
initialOptions: PadOption,
|
||||
userId: string,
|
||||
canEditPadSettings?: boolean,
|
||||
enablePadWideSettings?: boolean,
|
||||
mode: string,
|
||||
randomVersionString: string,
|
||||
skinName: string
|
||||
|
|
@ -184,6 +186,7 @@ export type ClientReadyMessage = {
|
|||
sessionID: string,
|
||||
token: string,
|
||||
userInfo: UserInfo,
|
||||
padSettingsDefaults?: PadOption,
|
||||
reconnect?: boolean
|
||||
client_rev?: number
|
||||
}
|
||||
|
|
@ -249,7 +252,7 @@ export type PadOption = {
|
|||
"alwaysShowChat"?: boolean,
|
||||
"chatAndUsers"?: boolean,
|
||||
"lang"?: null|string,
|
||||
view? : MapArrayType<boolean>
|
||||
view? : MapArrayType<boolean|string>
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -322,4 +325,3 @@ export type SocketClientReadyMessage = {
|
|||
reconnect?: boolean
|
||||
client_rev?: number
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -55,3 +55,20 @@ button, .btn
|
|||
box-shadow: none;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #d1242f;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-danger:hover {
|
||||
background: #b71c26;
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), 0 1px 2px rgba(0,0,0,0.12);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.btn-danger:disabled {
|
||||
background: #aaa;
|
||||
color: #fff;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,6 +26,20 @@
|
|||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.settings-sections {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
}
|
||||
|
||||
.settings-section {
|
||||
margin-bottom: 20px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
#settings .settings-section > h2 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.popup:not(.comment-modal) p {
|
||||
margin: 10px 0;
|
||||
}
|
||||
|
|
@ -41,6 +55,21 @@
|
|||
min-width: 180px;
|
||||
}
|
||||
|
||||
.settings-notice {
|
||||
color: var(--text-color);
|
||||
font-size: 0.95rem;
|
||||
line-height: 1.4;
|
||||
border-left: 3px solid var(--primary-color);
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
@media (min-width: 1100px) {
|
||||
.settings-sections.has-pad-settings {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
|
||||
align-items: start;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion) {
|
||||
.popup>.popup-content {
|
||||
transform: scale(1);
|
||||
|
|
@ -83,45 +112,5 @@
|
|||
}
|
||||
|
||||
#delete-pad {
|
||||
background-color: #ff7b72;
|
||||
}
|
||||
|
||||
#theme-switcher div {
|
||||
position: relative;
|
||||
width: 30px;
|
||||
background-color: white;
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
#theme-switcher {
|
||||
display: flex;
|
||||
margin-top: 20px;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
#theme-switcher div span {
|
||||
width: 15px;
|
||||
display: block;
|
||||
height: 15px;
|
||||
border-radius: 20px;
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
background-color: white;
|
||||
transition: background-color 0.25s;
|
||||
}
|
||||
|
||||
html.super-light-editor #theme-switcher div {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
html.super-light-editor #theme-switcher div span {
|
||||
left: 0;
|
||||
background-color: var(--primary-color);;
|
||||
}
|
||||
|
||||
html.super-dark-editor #theme-switcher div span {
|
||||
right: 0;
|
||||
background-color: var(--primary-color);;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -115,68 +115,133 @@
|
|||
<!------------------------------------------------------------->
|
||||
|
||||
<div id="settings" class="popup"><div class="popup-content">
|
||||
<h1 data-l10n-id="pad.settings.padSettings"></h1>
|
||||
<% e.begin_block("mySettings"); %>
|
||||
<h2 data-l10n-id="pad.settings.myView"></h2>
|
||||
<p class="hide-for-mobile">
|
||||
<input type="checkbox" id="options-stickychat">
|
||||
<label for="options-stickychat" data-l10n-id="pad.settings.stickychat"></label>
|
||||
</p>
|
||||
<p class="hide-for-mobile">
|
||||
<input type="checkbox" id="options-chatandusers" onClick="chat.chatAndUsers();">
|
||||
<label for="options-chatandusers" data-l10n-id="pad.settings.chatandusers"></label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" id="options-colorscheck">
|
||||
<label for="options-colorscheck" data-l10n-id="pad.settings.colorcheck"></label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" id="options-linenoscheck" checked>
|
||||
<label for="options-linenoscheck" data-l10n-id="pad.settings.linenocheck"></label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" id="options-rtlcheck">
|
||||
<label for="options-rtlcheck" data-l10n-id="pad.settings.rtlcheck"></label>
|
||||
</p>
|
||||
<% e.end_block(); %>
|
||||
|
||||
<div class="dropdowns-container">
|
||||
<% e.begin_block("mySettings.dropdowns"); %>
|
||||
<p class="dropdown-line">
|
||||
<label for="viewfontmenu" data-l10n-id="pad.settings.fontType">Font type:</label>
|
||||
<select id="viewfontmenu">
|
||||
<option value="" data-l10n-id="pad.settings.fontType.normal">Normal</option>
|
||||
<%= fonts = ["Quicksand", "Roboto", "Alegreya", "PlayfairDisplay", "Montserrat", "OpenDyslexic", "RobotoMono"] %>
|
||||
<% for(var i=0; i < fonts.length; i++) { %>
|
||||
<option value="<%=fonts[i]%>"><%=fonts[i]%></option>
|
||||
<% } %>
|
||||
</select>
|
||||
<% if (settings.enablePadWideSettings) { %>
|
||||
<h1 data-l10n-id="pad.settings.title">Settings</h1>
|
||||
<% } else { %>
|
||||
<h1 data-l10n-id="pad.settings.padSettings">Pad Settings</h1>
|
||||
<% } %>
|
||||
<div class="settings-sections">
|
||||
<div id="user-settings-section" class="settings-section">
|
||||
<% e.begin_block("mySettings"); %>
|
||||
<% if (settings.enablePadWideSettings) { %>
|
||||
<h2 data-l10n-id="pad.settings.userSettings"></h2>
|
||||
<% } else { %>
|
||||
<h2 data-l10n-id="pad.settings.myView"></h2>
|
||||
<% } %>
|
||||
<p class="hide-for-mobile">
|
||||
<input type="checkbox" id="options-disablechat">
|
||||
<label for="options-disablechat" data-l10n-id="pad.settings.disablechat">Disable Chat</label>
|
||||
</p>
|
||||
|
||||
<p class="dropdown-line">
|
||||
<label for="languagemenu" data-l10n-id="pad.settings.language">Language:</label>
|
||||
<select id="languagemenu">
|
||||
<% for (lang in langs) { %>
|
||||
<option value="<%=lang%>"><%=langs[lang].nativeName%></option>
|
||||
<% } %>
|
||||
</select>
|
||||
<p class="hide-for-mobile">
|
||||
<input type="checkbox" id="options-stickychat">
|
||||
<label for="options-stickychat" data-l10n-id="pad.settings.stickychat"></label>
|
||||
</p>
|
||||
<p class="hide-for-mobile">
|
||||
<input type="checkbox" id="options-chatandusers">
|
||||
<label for="options-chatandusers" data-l10n-id="pad.settings.chatandusers"></label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" id="options-colorscheck">
|
||||
<label for="options-colorscheck" data-l10n-id="pad.settings.colorcheck"></label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" id="options-linenoscheck" checked>
|
||||
<label for="options-linenoscheck" data-l10n-id="pad.settings.linenocheck"></label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" id="options-rtlcheck">
|
||||
<label for="options-rtlcheck" data-l10n-id="pad.settings.rtlcheck"></label>
|
||||
</p>
|
||||
<p id="theme-toggle-row" hidden>
|
||||
<input type="checkbox" id="options-darkmode">
|
||||
<label for="options-darkmode" data-l10n-id="pad.settings.darkMode">Dark mode</label>
|
||||
</p>
|
||||
<% if (settings.enablePadWideSettings) { %><p id="enforce-settings-notice" class="settings-notice" hidden
|
||||
data-l10n-id="pad.settings.enforcedNotice">
|
||||
These settings are locked for you by this pad's creator. Ask the pad creator if you need them changed.
|
||||
</p><% } %>
|
||||
<% e.end_block(); %>
|
||||
</div>
|
||||
<button data-l10n-id="pad.settings.deletePad" id="delete-pad">Delete pad</button>
|
||||
<div id="theme-switcher" style="display: none;">
|
||||
<svg width="2rem" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
|
||||
</svg>
|
||||
<div>
|
||||
<span aria-label="theme-switcher-knob"></span>
|
||||
</div>
|
||||
<svg width="2rem" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="size-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="dropdowns-container">
|
||||
<% e.begin_block("mySettings.dropdowns"); %>
|
||||
<p class="dropdown-line">
|
||||
<label for="viewfontmenu" data-l10n-id="pad.settings.fontType">Font type:</label>
|
||||
<select id="viewfontmenu">
|
||||
<option value="" data-l10n-id="pad.settings.fontType.normal">Normal</option>
|
||||
<%= fonts = ["Quicksand", "Roboto", "Alegreya", "PlayfairDisplay", "Montserrat", "OpenDyslexic", "RobotoMono"] %>
|
||||
<% for(var i=0; i < fonts.length; i++) { %>
|
||||
<option value="<%=fonts[i]%>"><%=fonts[i]%></option>
|
||||
<% } %>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p class="dropdown-line">
|
||||
<label for="languagemenu" data-l10n-id="pad.settings.language">Language:</label>
|
||||
<select id="languagemenu">
|
||||
<% for (lang in langs) { %>
|
||||
<option value="<%=lang%>"><%=langs[lang].nativeName%></option>
|
||||
<% } %>
|
||||
</select>
|
||||
</p>
|
||||
<% e.end_block(); %>
|
||||
</div>
|
||||
</div>
|
||||
<% if (settings.enablePadWideSettings) { %><div id="pad-settings-section" class="settings-section" hidden>
|
||||
<% e.begin_block("padSettings"); %>
|
||||
<h2 data-l10n-id="pad.settings.padSettings"></h2>
|
||||
<p class="hide-for-mobile">
|
||||
<input type="checkbox" id="padsettings-options-disablechat">
|
||||
<label for="padsettings-options-disablechat" data-l10n-id="pad.settings.disablechat">Disable Chat</label>
|
||||
</p>
|
||||
<p class="hide-for-mobile">
|
||||
<input type="checkbox" id="padsettings-options-stickychat">
|
||||
<label for="padsettings-options-stickychat" data-l10n-id="pad.settings.stickychat"></label>
|
||||
</p>
|
||||
<p class="hide-for-mobile">
|
||||
<input type="checkbox" id="padsettings-options-chatandusers">
|
||||
<label for="padsettings-options-chatandusers" data-l10n-id="pad.settings.chatandusers"></label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" id="padsettings-options-colorscheck">
|
||||
<label for="padsettings-options-colorscheck" data-l10n-id="pad.settings.colorcheck"></label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" id="padsettings-options-linenoscheck" checked>
|
||||
<label for="padsettings-options-linenoscheck" data-l10n-id="pad.settings.linenocheck"></label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" id="padsettings-options-rtlcheck">
|
||||
<label for="padsettings-options-rtlcheck" data-l10n-id="pad.settings.rtlcheck"></label>
|
||||
</p>
|
||||
<p>
|
||||
<input type="checkbox" id="padsettings-enforcecheck">
|
||||
<label for="padsettings-enforcecheck" data-l10n-id="pad.settings.enforceSettings">Enforce settings for other users</label>
|
||||
</p>
|
||||
<% e.end_block(); %>
|
||||
<div class="dropdowns-container">
|
||||
<% e.begin_block("padSettings.dropdowns"); %>
|
||||
<p class="dropdown-line">
|
||||
<label for="padsettings-viewfontmenu" data-l10n-id="pad.settings.fontType">Font type:</label>
|
||||
<select id="padsettings-viewfontmenu">
|
||||
<option value="" data-l10n-id="pad.settings.fontType.normal">Normal</option>
|
||||
<% for(var i=0; i < fonts.length; i++) { %>
|
||||
<option value="<%=fonts[i]%>"><%=fonts[i]%></option>
|
||||
<% } %>
|
||||
</select>
|
||||
</p>
|
||||
<p class="dropdown-line">
|
||||
<label for="padsettings-languagemenu" data-l10n-id="pad.settings.language">Language:</label>
|
||||
<select id="padsettings-languagemenu">
|
||||
<% for (lang in langs) { %>
|
||||
<option value="<%=lang%>"><%=langs[lang].nativeName%></option>
|
||||
<% } %>
|
||||
</select>
|
||||
</p>
|
||||
<% e.end_block(); %>
|
||||
</div>
|
||||
<button class="btn btn-danger" data-l10n-id="pad.settings.deletePad" id="delete-pad">Delete pad</button>
|
||||
</div><% } %>
|
||||
</div>
|
||||
<h2 data-l10n-id="pad.settings.about">About</h2>
|
||||
<span data-l10n-id="pad.settings.poweredBy">Powered by</span>
|
||||
<a href="https://etherpad.org" target="_blank" referrerpolicy="no-referrer" rel="noopener">Etherpad</a>
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ test.describe('font select', function () {
|
|||
// commenting out above will break safari test
|
||||
const dropdown = page.locator('.dropdowns-container .dropdown-line .current').nth(0)
|
||||
await dropdown.click()
|
||||
await page.locator('li:text("RobotoMono")').click()
|
||||
await page.locator('.nice-select.open li:text("RobotoMono")').click()
|
||||
|
||||
await viewFontMenu.dispatchEvent('change');
|
||||
const padBody = await getPadBody(page)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ test.describe('Language select and change', function () {
|
|||
const languageDropDown = page.locator('.nice-select').nth(1)
|
||||
|
||||
await languageDropDown.click()
|
||||
await page.locator('.nice-select').locator('[data-value=de]').click()
|
||||
await page.locator('.nice-select.open').locator('[data-value=de]').click()
|
||||
await expect(languageDropDown.locator('.current')).toHaveText('Deutsch')
|
||||
|
||||
// select german
|
||||
|
|
@ -34,7 +34,7 @@ test.describe('Language select and change', function () {
|
|||
|
||||
// click the language button
|
||||
await page.locator('.nice-select').nth(1).locator('.current').click()
|
||||
await page.locator('.nice-select').locator('[data-value=de]').click()
|
||||
await page.locator('.nice-select.open').locator('[data-value=de]').click()
|
||||
|
||||
// select german
|
||||
await page.locator('.buttonicon-bold').evaluate((el) => el.parentElement!.title === 'Fett (Strg-B)');
|
||||
|
|
@ -42,7 +42,7 @@ test.describe('Language select and change', function () {
|
|||
|
||||
// change to english
|
||||
await page.locator('.nice-select').nth(1).locator('.current').click()
|
||||
await page.locator('.nice-select').locator('[data-value=en]').click()
|
||||
await page.locator('.nice-select.open').locator('[data-value=en]').click()
|
||||
|
||||
// check if the language is now English
|
||||
await page.locator('.buttonicon-bold').evaluate((el) => el.parentElement!.title !== 'Fett (Strg-B)');
|
||||
|
|
@ -54,7 +54,7 @@ test.describe('Language select and change', function () {
|
|||
|
||||
// click the language button
|
||||
await page.locator('.nice-select').nth(1).locator('.current').click()
|
||||
await page.locator('.nice-select').locator('[data-value=de]').click()
|
||||
await page.locator('.nice-select.open').locator('[data-value=de]').click()
|
||||
|
||||
// select german
|
||||
await page.locator('.buttonicon-bold').evaluate((el) => el.parentElement!.title === 'Fett (Strg-B)');
|
||||
|
|
@ -63,7 +63,7 @@ test.describe('Language select and change', function () {
|
|||
await page.locator('.nice-select').nth(1).locator('.current').click()
|
||||
// select arabic
|
||||
// $languageoption.attr('selected','selected'); // Breaks the test..
|
||||
await page.locator('.nice-select').locator('[data-value=ar]').click()
|
||||
await page.locator('.nice-select.open').locator('[data-value=ar]').click()
|
||||
|
||||
await page.waitForSelector('html[dir="rtl"]')
|
||||
});
|
||||
|
|
|
|||
163
src/tests/frontend-new/specs/pad_settings.spec.ts
Normal file
163
src/tests/frontend-new/specs/pad_settings.spec.ts
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
import {expect, test} from "@playwright/test";
|
||||
import {goToNewPad, goToPad, sendChatMessage, showChat} from "../helper/padHelper";
|
||||
import {showSettings} from "../helper/settingsHelper";
|
||||
|
||||
test.describe('creator-owned pad settings', () => {
|
||||
test('shows pad settings only to the creator and keeps delete pad there', async ({page, browser}) => {
|
||||
const padId = await goToNewPad(page);
|
||||
|
||||
const context2 = await browser.newContext();
|
||||
const page2 = await context2.newPage();
|
||||
await goToPad(page2, padId);
|
||||
|
||||
await showSettings(page);
|
||||
await showSettings(page2);
|
||||
|
||||
await expect(page.locator('#user-settings-section > h2')).toHaveText('User Settings');
|
||||
await expect(page.locator('#pad-settings-section > h2')).toHaveText('Pad-wide Settings');
|
||||
await expect(page.locator('#theme-toggle-row')).toBeVisible();
|
||||
await expect(page.locator('#pad-settings-section')).toBeVisible();
|
||||
await expect(page.locator('#delete-pad')).toBeVisible();
|
||||
await expect(page.locator('#padsettings-enforcecheck')).toBeVisible();
|
||||
|
||||
await expect(page2.locator('#user-settings-section > h2')).toHaveText('User Settings');
|
||||
await expect(page2.locator('#theme-toggle-row')).toBeVisible();
|
||||
await expect(page2.locator('#pad-settings-section')).toBeHidden();
|
||||
await expect(page2.locator('#delete-pad')).toBeHidden();
|
||||
|
||||
await context2.close();
|
||||
});
|
||||
|
||||
test('pad settings act as defaults until enforcement is enabled', async ({page, browser}) => {
|
||||
const padId = await goToNewPad(page);
|
||||
|
||||
const context2 = await browser.newContext();
|
||||
const page2 = await context2.newPage();
|
||||
await goToPad(page2, padId);
|
||||
|
||||
const creatorOuter = page.frameLocator('iframe[name="ace_outer"]').locator('#outerdocbody');
|
||||
const creatorInner = page.frameLocator('iframe[name="ace_outer"]')
|
||||
.frameLocator('iframe[name="ace_inner"]').locator('body');
|
||||
const viewerOuter = page2.frameLocator('iframe[name="ace_outer"]').locator('#outerdocbody');
|
||||
const viewerInner = page2.frameLocator('iframe[name="ace_outer"]')
|
||||
.frameLocator('iframe[name="ace_inner"]').locator('body');
|
||||
|
||||
await expect(creatorOuter).not.toHaveClass(/line-numbers-hidden/);
|
||||
await expect(creatorInner).toHaveClass(/authorColors/);
|
||||
await expect(viewerOuter).not.toHaveClass(/line-numbers-hidden/);
|
||||
await expect(viewerInner).toHaveClass(/authorColors/);
|
||||
|
||||
await showSettings(page);
|
||||
await page.locator('label[for="padsettings-options-linenoscheck"]').click();
|
||||
await expect(page.locator('#padsettings-options-linenoscheck')).not.toBeChecked();
|
||||
await expect(creatorOuter).toHaveClass(/line-numbers-hidden/);
|
||||
await expect(viewerOuter).toHaveClass(/line-numbers-hidden/);
|
||||
|
||||
await page.locator('label[for="padsettings-options-colorscheck"]').click();
|
||||
await expect(page.locator('#padsettings-options-colorscheck')).not.toBeChecked();
|
||||
await expect(creatorInner).not.toHaveClass(/authorColors/);
|
||||
await expect(viewerInner).not.toHaveClass(/authorColors/);
|
||||
|
||||
await expect(page.locator('#options-linenoscheck')).not.toBeChecked();
|
||||
await expect(page.locator('#options-colorscheck')).not.toBeChecked();
|
||||
|
||||
await showSettings(page2);
|
||||
await page2.locator('label[for="options-linenoscheck"]').click();
|
||||
await page2.locator('label[for="options-colorscheck"]').click();
|
||||
await expect(viewerOuter).not.toHaveClass(/line-numbers-hidden/, {timeout: 1000});
|
||||
await expect(viewerInner).toHaveClass(/authorColors/);
|
||||
await expect(page2.locator('#options-linenoscheck')).toBeChecked();
|
||||
await expect(page2.locator('#options-colorscheck')).toBeChecked();
|
||||
|
||||
await expect(creatorOuter).toHaveClass(/line-numbers-hidden/);
|
||||
await expect(creatorInner).not.toHaveClass(/authorColors/);
|
||||
|
||||
await page.locator('label[for="padsettings-enforcecheck"]').click();
|
||||
await expect(page.locator('#padsettings-enforcecheck')).toBeChecked();
|
||||
await expect(viewerOuter).toHaveClass(/line-numbers-hidden/);
|
||||
await expect(viewerInner).not.toHaveClass(/authorColors/);
|
||||
await expect(page2.locator('#options-linenoscheck')).not.toBeChecked();
|
||||
await expect(page2.locator('#options-colorscheck')).not.toBeChecked();
|
||||
await expect(page.locator('#options-linenoscheck')).toBeEnabled();
|
||||
await expect(page.locator('#options-colorscheck')).toBeEnabled();
|
||||
await expect(page2.locator('#enforce-settings-notice')).toBeVisible();
|
||||
await expect(page.locator('#enforce-settings-notice')).toBeHidden();
|
||||
await context2.close();
|
||||
});
|
||||
|
||||
test('creator can keep authorship colors while pad-wide enforced settings keep them off for other users',
|
||||
async ({page, browser}) => {
|
||||
const padId = await goToNewPad(page);
|
||||
|
||||
const context2 = await browser.newContext();
|
||||
const page2 = await context2.newPage();
|
||||
await goToPad(page2, padId);
|
||||
|
||||
const creatorInner = page.frameLocator('iframe[name="ace_outer"]')
|
||||
.frameLocator('iframe[name="ace_inner"]').locator('body');
|
||||
const viewerInner = page2.frameLocator('iframe[name="ace_outer"]')
|
||||
.frameLocator('iframe[name="ace_inner"]').locator('body');
|
||||
|
||||
await expect(creatorInner).toHaveClass(/authorColors/);
|
||||
await expect(viewerInner).toHaveClass(/authorColors/);
|
||||
|
||||
await showSettings(page);
|
||||
await page.locator('label[for="padsettings-options-colorscheck"]').click();
|
||||
await expect(page.locator('#padsettings-options-colorscheck')).not.toBeChecked();
|
||||
await expect(page.locator('#options-colorscheck')).not.toBeChecked();
|
||||
await expect(creatorInner).not.toHaveClass(/authorColors/);
|
||||
await expect(viewerInner).not.toHaveClass(/authorColors/);
|
||||
|
||||
await page.locator('label[for="padsettings-enforcecheck"]').click();
|
||||
await expect(page.locator('#padsettings-enforcecheck')).toBeChecked();
|
||||
await showSettings(page2);
|
||||
await expect(page2.locator('#enforce-settings-notice')).toBeVisible();
|
||||
await expect(page.locator('#enforce-settings-notice')).toBeHidden();
|
||||
|
||||
await page.locator('label[for="options-colorscheck"]').click();
|
||||
await expect(page.locator('#options-colorscheck')).toBeChecked();
|
||||
await expect(creatorInner).toHaveClass(/authorColors/);
|
||||
await expect(viewerInner).not.toHaveClass(/authorColors/);
|
||||
|
||||
await context2.close();
|
||||
});
|
||||
|
||||
test('uses My View defaults for newly created pads without changing an existing pad default',
|
||||
async ({page}) => {
|
||||
await goToNewPad(page);
|
||||
const creatorOuter = page.frameLocator('iframe[name="ace_outer"]').locator('#outerdocbody');
|
||||
const creatorInner = page.frameLocator('iframe[name="ace_outer"]')
|
||||
.frameLocator('iframe[name="ace_inner"]').locator('body');
|
||||
|
||||
await showSettings(page);
|
||||
await page.locator('label[for="options-linenoscheck"]').click();
|
||||
await page.locator('label[for="options-colorscheck"]').click();
|
||||
await expect(page.locator('#options-linenoscheck')).not.toBeChecked();
|
||||
await expect(page.locator('#options-colorscheck')).not.toBeChecked();
|
||||
await expect(creatorOuter).toHaveClass(/line-numbers-hidden/);
|
||||
await expect(creatorInner).not.toHaveClass(/authorColors/);
|
||||
|
||||
await goToNewPad(page);
|
||||
await showSettings(page);
|
||||
await expect(page.locator('#options-linenoscheck')).not.toBeChecked();
|
||||
await expect(page.locator('#options-colorscheck')).not.toBeChecked();
|
||||
});
|
||||
|
||||
test('disabling chat suppresses chat gritter notifications', async ({page, browser}) => {
|
||||
const padId = await goToNewPad(page);
|
||||
const context2 = await browser.newContext();
|
||||
const page2 = await context2.newPage();
|
||||
await goToPad(page2, padId);
|
||||
|
||||
await showSettings(page);
|
||||
await page.locator('label[for="options-disablechat"]').click();
|
||||
await expect(page.locator('#options-disablechat')).toBeChecked();
|
||||
await expect(page.locator('#chaticon')).toBeHidden();
|
||||
|
||||
await showChat(page2);
|
||||
await sendChatMessage(page2, 'hello from user 2');
|
||||
await expect(page.locator('.chat-gritter-msg')).toHaveCount(0);
|
||||
|
||||
await context2.close();
|
||||
});
|
||||
});
|
||||
|
|
@ -23,15 +23,15 @@ test.describe('RTL URL parameter', function () {
|
|||
await expect(page.locator('#options-rtlcheck')).not.toBeChecked();
|
||||
});
|
||||
|
||||
test('no rtl param preserves cookie-based RTL preference', async function ({page}) {
|
||||
// Enable RTL via URL (which also sets the cookie)
|
||||
test('no rtl param falls back to the pad setting after an RTL URL override', async function ({page}) {
|
||||
// Enable RTL via URL for the current page load only
|
||||
await appendQueryParams(page, {rtl: 'true'});
|
||||
await expect(page.locator('#options-rtlcheck')).toBeChecked();
|
||||
|
||||
// Reload without rtl param — cookie should preserve RTL
|
||||
// Reload without rtl param — the pad setting remains authoritative
|
||||
const url = page.url().replace(/[?&]rtl=true/, '');
|
||||
await page.goto(url);
|
||||
await page.waitForSelector('#editorcontainer.initialized');
|
||||
await expect(page.locator('#options-rtlcheck')).toBeChecked();
|
||||
await expect(page.locator('#options-rtlcheck')).not.toBeChecked();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
{"title":"Etherpad","favicon":null,"skinName":"colibris","skinVariants":"super-light-toolbar super-light-editor light-background","ip":"0.0.0.0","port":9001,"showSettingsInAdminPage":true,"dbType":"dirty","dbSettings":{"filename":"var/dirty.db"},"defaultPadText":"Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https://etherpad.org\n","padOptions":{"noColors":false,"showControls":true,"showChat":true,"showLineNumbers":true,"useMonospaceFont":false,"userName":null,"userColor":null,"rtl":false,"alwaysShowChat":false,"chatAndUsers":false,"lang":null},"padShortcutEnabled":{"altF9":true,"altC":true,"cmdShift2":true,"delete":true,"return":true,"esc":true,"cmdS":true,"tab":true,"cmdZ":true,"cmdY":true,"cmdI":true,"cmdB":true,"cmdU":true,"cmd5":true,"cmdShiftL":true,"cmdShiftN":true,"cmdShift1":true,"cmdShiftC":true,"cmdH":true,"ctrlHome":true,"pageUp":true,"pageDown":true},"suppressErrorsInPadText":false,"requireSession":false,"editOnly":false,"minify":true,"maxAge":21600,"soffice":null,"allowUnknownFileEnds":true,"requireAuthentication":false,"requireAuthorization":false,"trustProxy":false,"cookie":{"keyRotationInterval":86400000,"sameSite":"Lax","sessionLifetime":864000000,"sessionRefreshInterval":86400000},"disableIPlogging":false,"automaticReconnectionTimeout":0,"scrollWhenFocusLineIsOutOfViewport":{"percentage":{"editionAboveViewport":0,"editionBelowViewport":0},"duration":0,"scrollWhenCaretIsInTheLastLineOfViewport":false,"percentageToScrollWhenUserPressesArrowUp":0},"users":{"admin":{"password":"changeme1","is_admin":true},"user":{"password":"changeme1","is_admin":false}},"socketTransportProtocols":["websocket","polling"],"socketIo":{"maxHttpBufferSize":1000000},"loadTest":false,"dumpOnUncleanExit":false,"importExportRateLimiting":{"windowMs":90000,"max":10},"importMaxFileSize":52428800,"commitRateLimiting":{"duration":1,"points":10},"exposeVersion":false,"loglevel":"INFO","customLocaleStrings":{},"enableAdminUITests":true,"lowerCasePadIds":false,"sso":{"issuer":"${SSO_ISSUER:http://localhost:9001}","clients":[{"client_id":"${ADMIN_CLIENT:admin_client}","client_secret":"${ADMIN_SECRET:admin}","grant_types":["authorization_code"],"response_types":["code"],"redirect_uris":["${ADMIN_REDIRECT:http://localhost:9001/admin/}","https://oauth.pstmn.io/v1/callback"]},{"client_id":"${USER_CLIENT:user_client}","client_secret":"${USER_SECRET:user}","grant_types":["authorization_code"],"response_types":["code"],"redirect_uris":["${USER_REDIRECT:http://localhost:9001/}"]}]}}
|
||||
{"title":"Etherpad","favicon":null,"skinName":"colibris","skinVariants":"super-light-toolbar super-light-editor light-background","ip":"0.0.0.0","port":9001,"showSettingsInAdminPage":true,"dbType":"dirty","dbSettings":{"filename":"var/dirty.db"},"defaultPadText":"Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nGet involved with Etherpad at https://etherpad.org\n","padOptions":{"noColors":false,"showControls":true,"showChat":true,"showLineNumbers":true,"useMonospaceFont":false,"userName":null,"userColor":null,"rtl":false,"alwaysShowChat":false,"chatAndUsers":false,"lang":null},"padShortcutEnabled":{"altF9":true,"altC":true,"cmdShift2":true,"delete":true,"return":true,"esc":true,"cmdS":true,"tab":true,"cmdZ":true,"cmdY":true,"cmdI":true,"cmdB":true,"cmdU":true,"cmd5":true,"cmdShiftL":true,"cmdShiftN":true,"cmdShift1":true,"cmdShiftC":true,"cmdH":true,"ctrlHome":true,"pageUp":true,"pageDown":true},"suppressErrorsInPadText":false,"requireSession":false,"editOnly":false,"minify":true,"maxAge":21600,"soffice":null,"allowUnknownFileEnds":true,"requireAuthentication":false,"requireAuthorization":false,"trustProxy":false,"cookie":{"keyRotationInterval":86400000,"sameSite":"Lax","sessionLifetime":864000000,"sessionRefreshInterval":86400000},"disableIPlogging":false,"automaticReconnectionTimeout":0,"scrollWhenFocusLineIsOutOfViewport":{"percentage":{"editionAboveViewport":0,"editionBelowViewport":0},"duration":0,"scrollWhenCaretIsInTheLastLineOfViewport":false,"percentageToScrollWhenUserPressesArrowUp":0},"users":{"admin":{"password":"changeme1","is_admin":true},"user":{"password":"changeme1","is_admin":false}},"socketTransportProtocols":["websocket","polling"],"socketIo":{"maxHttpBufferSize":1000000},"loadTest":false,"dumpOnUncleanExit":false,"importExportRateLimiting":{"windowMs":90000,"max":10},"importMaxFileSize":52428800,"commitRateLimiting":{"duration":1,"points":10},"exposeVersion":false,"loglevel":"INFO","customLocaleStrings":{},"enableAdminUITests":true,"enablePadWideSettings":true,"lowerCasePadIds":false,"sso":{"issuer":"${SSO_ISSUER:http://localhost:9001}","clients":[{"client_id":"${ADMIN_CLIENT:admin_client}","client_secret":"${ADMIN_SECRET:admin}","grant_types":["authorization_code"],"response_types":["code"],"redirect_uris":["${ADMIN_REDIRECT:http://localhost:9001/admin/}","https://oauth.pstmn.io/v1/callback"]},{"client_id":"${USER_CLIENT:user_client}","client_secret":"${USER_SECRET:user}","grant_types":["authorization_code"],"response_types":["code"],"redirect_uris":["${USER_REDIRECT:http://localhost:9001/}"]}]}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue