mirror of
https://github.com/ether/etherpad-lite.git
synced 2025-05-04 14:19:13 -04:00
Non working hmr
This commit is contained in:
parent
70092c1cb7
commit
d522de17e6
20 changed files with 2621 additions and 1185 deletions
685
pnpm-lock.yaml
generated
685
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
|
@ -10,10 +10,8 @@ const settings = require('../../utils/Settings');
|
|||
import util from 'node:util';
|
||||
const webaccess = require('./webaccess');
|
||||
const plugins = require('../../../static/js/pluginfw/plugin_defs');
|
||||
import {hash, createHash} from 'node:crypto'
|
||||
|
||||
|
||||
import {buildSync} from 'esbuild'
|
||||
import {build, buildSync} from 'esbuild'
|
||||
exports.expressPreSession = async (hookName:string, {app}:any) => {
|
||||
// This endpoint is intended to conform to:
|
||||
// https://www.ietf.org/archive/id/draft-inadarei-api-health-check-06.html
|
||||
|
@ -77,6 +75,54 @@ exports.expressPreSession = async (hookName:string, {app}:any) => {
|
|||
});
|
||||
};
|
||||
|
||||
|
||||
|
||||
const convertTypescript = (content: string) => {
|
||||
const outputRaw = buildSync({
|
||||
stdin: {
|
||||
contents: content,
|
||||
resolveDir: path.join(settings.root, 'var','js'),
|
||||
loader: 'js'
|
||||
},
|
||||
bundle: true, // Bundle the files together
|
||||
minify: process.env.NODE_ENV === "production", // Minify the output
|
||||
sourcemap: !(process.env.NODE_ENV === "production"), // Generate source maps
|
||||
sourceRoot: settings.root+"/src/static/js/",
|
||||
target: ['es2020'], // Target ECMAScript version
|
||||
metafile: true,
|
||||
write: false, // Do not write to file system,
|
||||
})
|
||||
const output = outputRaw.outputFiles[0].text
|
||||
|
||||
return {
|
||||
output,
|
||||
hash: outputRaw.outputFiles[0].hash.replaceAll('/','2')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const convertTypescriptWatched = (content: string, cb: (output:string, hash: string)=>void) => {
|
||||
build({
|
||||
stdin: {
|
||||
contents: content,
|
||||
resolveDir: path.join(settings.root, 'var','js'),
|
||||
loader: 'js'
|
||||
},
|
||||
bundle: true, // Bundle the files together
|
||||
minify: process.env.NODE_ENV === "production", // Minify the output
|
||||
sourcemap: !(process.env.NODE_ENV === "production"), // Generate source maps
|
||||
sourceRoot: settings.root+"/src/static/js/",
|
||||
target: ['es2020'], // Target ECMAScript version
|
||||
metafile: true,
|
||||
write: false, // Do not write to file system,
|
||||
}).then((outputRaw) => {
|
||||
cb(
|
||||
outputRaw.outputFiles[0].text,
|
||||
outputRaw.outputFiles[0].hash.replaceAll('/','2')
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
exports.expressCreateServer = async (hookName: string, args: any, cb: Function) => {
|
||||
// serve index.html under /
|
||||
args.app.get('/', (req: any, res: any) => {
|
||||
|
@ -98,7 +144,6 @@ exports.expressCreateServer = async (hookName: string, args: any, cb: Function)
|
|||
settings,
|
||||
})
|
||||
|
||||
|
||||
const timeSliderString = eejs.require('ep_etherpad-lite/templates/timeSliderBootstrap.js', {
|
||||
pluginModules: (() => {
|
||||
const pluginModules = new Set();
|
||||
|
@ -117,97 +162,128 @@ exports.expressCreateServer = async (hookName: string, args: any, cb: Function)
|
|||
|
||||
const outdir = path.join(settings.root, 'var','js')
|
||||
|
||||
const padWriteResult = buildSync({
|
||||
stdin: {
|
||||
contents: padString,
|
||||
resolveDir: path.join(settings.root, 'var','js'),
|
||||
loader: 'js'
|
||||
}, // Entry file(s)
|
||||
bundle: true, // Bundle the files together
|
||||
minify: process.env.NODE_ENV === "production", // Minify the output
|
||||
sourcemap: !(process.env.NODE_ENV === "production"), // Generate source maps
|
||||
sourceRoot: settings.root+"/src/static/js/",
|
||||
target: ['es2020'], // Target ECMAScript version
|
||||
metafile: true,
|
||||
write: false, // Do not write to file system,
|
||||
})
|
||||
let fileNamePad: string
|
||||
let fileNameTimeSlider: string
|
||||
if(process.env.NODE_ENV === "production"){
|
||||
const padSliderWrite = convertTypescript(padString)
|
||||
const timeSliderWrite = convertTypescript(timeSliderString)
|
||||
|
||||
const outputPadJS = padWriteResult.outputFiles[0].text
|
||||
fileNamePad = `padbootstrap-${padSliderWrite.hash}.min.js`
|
||||
fileNameTimeSlider = `timeSliderBootstrap-${timeSliderWrite.hash}.min.js`
|
||||
const pathNamePad = path.join(outdir, fileNamePad)
|
||||
const pathNameTimeSlider = path.join(outdir, fileNameTimeSlider)
|
||||
|
||||
const timeSliderWrite = buildSync({
|
||||
stdin: {
|
||||
contents: timeSliderString,
|
||||
resolveDir: path.join(settings.root, 'var','js'),
|
||||
loader: 'js'
|
||||
},
|
||||
bundle: true, // Bundle the files together
|
||||
minify: process.env.NODE_ENV === "production", // Minify the output
|
||||
sourcemap: !(process.env.NODE_ENV === "production"), // Generate source maps
|
||||
sourceRoot: settings.root+"/src/static/js/",
|
||||
target: ['es2020'], // Target ECMAScript version
|
||||
metafile: true,
|
||||
write: false, // Do not write to file system,
|
||||
})
|
||||
if (!fs.existsSync(pathNamePad)) {
|
||||
fs.writeFileSync(pathNamePad, padSliderWrite.output);
|
||||
}
|
||||
|
||||
const outputTimeslider = timeSliderWrite.outputFiles[0].text
|
||||
if (!fs.existsSync(pathNameTimeSlider)) {
|
||||
fs.writeFileSync(pathNameTimeSlider,timeSliderWrite.output)
|
||||
}
|
||||
|
||||
const hash = padWriteResult.outputFiles[0].hash
|
||||
const hashTimeSlider = timeSliderWrite.outputFiles[0].hash
|
||||
args.app.get("/"+fileNamePad, (req: any, res: any) => {
|
||||
res.sendFile(pathNamePad)
|
||||
})
|
||||
|
||||
const fileNamePad = `padbootstrap-${hash}.min.js`
|
||||
const fileNameTimeSlider = `timeSliderBootstrap-${hashTimeSlider}.min.js`
|
||||
const pathNamePad = path.join(outdir, fileNamePad)
|
||||
const pathNameTimeSlider = path.join(outdir, fileNameTimeSlider)
|
||||
|
||||
if (!fs.existsSync(pathNamePad)) {
|
||||
fs.writeFileSync(pathNamePad, outputPadJS);
|
||||
}
|
||||
|
||||
if (!fs.existsSync(pathNameTimeSlider)) {
|
||||
fs.writeFileSync(pathNameTimeSlider,outputTimeslider)
|
||||
}
|
||||
|
||||
args.app.get("/"+fileNamePad, (req: any, res: any) => {
|
||||
res.sendFile(pathNamePad)
|
||||
})
|
||||
|
||||
args.app.get("/"+fileNameTimeSlider, (req: any, res: any) => {
|
||||
res.sendFile(pathNameTimeSlider)
|
||||
})
|
||||
args.app.get("/"+fileNameTimeSlider, (req: any, res: any) => {
|
||||
res.sendFile(pathNameTimeSlider)
|
||||
})
|
||||
|
||||
|
||||
// serve pad.html under /p
|
||||
args.app.get('/p/:pad', (req: any, res: any, next: Function) => {
|
||||
// The below might break for pads being rewritten
|
||||
const isReadOnly = !webaccess.userCanModify(req.params.pad, req);
|
||||
// serve pad.html under /p
|
||||
args.app.get('/p/:pad', (req: any, res: any, next: Function) => {
|
||||
// The below might break for pads being rewritten
|
||||
const isReadOnly = !webaccess.userCanModify(req.params.pad, req);
|
||||
|
||||
hooks.callAll('padInitToolbar', {
|
||||
toolbar,
|
||||
isReadOnly
|
||||
hooks.callAll('padInitToolbar', {
|
||||
toolbar,
|
||||
isReadOnly
|
||||
});
|
||||
|
||||
// can be removed when require-kernel is dropped
|
||||
res.header('Feature-Policy', 'sync-xhr \'self\'');
|
||||
const content = eejs.require('ep_etherpad-lite/templates/pad.html', {
|
||||
req,
|
||||
toolbar,
|
||||
isReadOnly,
|
||||
entrypoint: "/"+fileNamePad
|
||||
})
|
||||
res.send(content);
|
||||
});
|
||||
|
||||
// can be removed when require-kernel is dropped
|
||||
res.header('Feature-Policy', 'sync-xhr \'self\'');
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/pad.html', {
|
||||
req,
|
||||
toolbar,
|
||||
isReadOnly,
|
||||
entrypoint: "/"+fileNamePad
|
||||
}));
|
||||
});
|
||||
// serve timeslider.html under /p/$padname/timeslider
|
||||
args.app.get('/p/:pad/timeslider', (req: any, res: any, next: Function) => {
|
||||
hooks.callAll('padInitToolbar', {
|
||||
toolbar,
|
||||
});
|
||||
|
||||
// serve timeslider.html under /p/$padname/timeslider
|
||||
args.app.get('/p/:pad/timeslider', (req: any, res: any, next: Function) => {
|
||||
hooks.callAll('padInitToolbar', {
|
||||
toolbar,
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
|
||||
req,
|
||||
toolbar,
|
||||
entrypoint: "/"+fileNameTimeSlider
|
||||
}));
|
||||
});
|
||||
} else {
|
||||
const chokidar = await import('chokidar')
|
||||
const map = new Map<string, string>()
|
||||
const watcher = chokidar.watch(path.join(settings.root, 'src','static','js'));
|
||||
watcher.on('change', path => {
|
||||
console.log(`File ${path} has been changed`);
|
||||
convertTypescriptWatched(padString, (output, hash)=>{
|
||||
console.log("New hash is", hash)
|
||||
map.set('output', output)
|
||||
map.set('fileNamePad', `padbootstrap-${hash}.js`)
|
||||
});
|
||||
convertTypescriptWatched(timeSliderString, (output, hash)=>{
|
||||
// serve timeslider.html under /p/$padname/timeslider
|
||||
console.log("New hash is", hash)
|
||||
|
||||
args.app.get('/watch/timeslider', (req: any, res: any) => {
|
||||
res.header('Content-Type', 'application/javascript');
|
||||
res.send(output)
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
|
||||
req,
|
||||
toolbar,
|
||||
entrypoint: "/"+fileNameTimeSlider
|
||||
}));
|
||||
});
|
||||
args.app.get('/watch/pad', (req: any, res: any) => {
|
||||
res.header('Content-Type', 'application/javascript');
|
||||
res.send(map.get('output'))
|
||||
})
|
||||
// serve pad.html under /p
|
||||
args.app.get('/p/:pad', (req: any, res: any, next: Function) => {
|
||||
console.log("Reloading pad")
|
||||
// The below might break for pads being rewritten
|
||||
const isReadOnly = !webaccess.userCanModify(req.params.pad, req);
|
||||
|
||||
hooks.callAll('padInitToolbar', {
|
||||
toolbar,
|
||||
isReadOnly
|
||||
});
|
||||
|
||||
// can be removed when require-kernel is dropped
|
||||
res.header('Feature-Policy', 'sync-xhr \'self\'');
|
||||
const content = eejs.require('ep_etherpad-lite/templates/pad.html', {
|
||||
req,
|
||||
toolbar,
|
||||
isReadOnly,
|
||||
entrypoint: map.get('fileNamePad')
|
||||
})
|
||||
res.send(content);
|
||||
});
|
||||
args.app.get('/p/:pad/timeslider', (req: any, res: any, next: Function) => {
|
||||
hooks.callAll('padInitToolbar', {
|
||||
toolbar,
|
||||
});
|
||||
let timeSliderFileName = "/watch/timeslider?hash="+map.get('fileNamePad')
|
||||
|
||||
res.send(eejs.require('ep_etherpad-lite/templates/timeslider.html', {
|
||||
req,
|
||||
toolbar,
|
||||
entrypoint: timeSliderFileName
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// The client occasionally polls this endpoint to get an updated expiration for the express_sid
|
||||
// cookie. This handler must be installed after the express-session middleware.
|
||||
|
|
|
@ -109,7 +109,8 @@
|
|||
"sinon": "^18.0.0",
|
||||
"split-grid": "^1.0.11",
|
||||
"supertest": "^7.0.0",
|
||||
"typescript": "^5.5.3"
|
||||
"typescript": "^5.5.3",
|
||||
"chokidar": "^3.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.18.2",
|
||||
|
|
|
@ -32,6 +32,9 @@ const colorutils = require('./colorutils').colorutils;
|
|||
const _ = require('./underscore');
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
|
||||
import html10n from './vendors/html10n';
|
||||
|
||||
|
||||
// These parameters were global, now they are injected. A reference to the
|
||||
// Timeslider controller would probably be more appropriate.
|
||||
const loadBroadcastJS = (socket, sendSocketMsg, fireWhenAllScriptsAreLoaded, BroadcastSlider) => {
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
const _ = require('./underscore');
|
||||
const padmodals = require('./pad_modals').padmodals;
|
||||
const colorutils = require('./colorutils').colorutils;
|
||||
import html10n from './vendors/html10n';
|
||||
|
||||
const loadBroadcastSliderJS = (fireWhenAllScriptsAreLoaded) => {
|
||||
let BroadcastSlider;
|
||||
|
|
|
@ -21,10 +21,12 @@ const padcookie = require('./pad_cookie').padcookie;
|
|||
const Tinycon = require('tinycon/tinycon');
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
const padeditor = require('./pad_editor').padeditor;
|
||||
import html10n from './vendors/html10n';
|
||||
|
||||
// Removes diacritics and lower-cases letters. https://stackoverflow.com/a/37511463
|
||||
const normalize = (s) => s.normalize('NFD').replace(/[\u0300-\u036f]/g, '').toLowerCase();
|
||||
|
||||
|
||||
exports.chat = (() => {
|
||||
let isStuck = false;
|
||||
let userAndChat = false;
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
import html10n from '../js/vendors/html10n';
|
||||
|
||||
|
||||
((document) => {
|
||||
// Set language for l10n
|
||||
let language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/);
|
||||
if (language) language = language[1];
|
||||
|
||||
html10n.bind('indexed', () => {
|
||||
html10n.localize([language, navigator.language, navigator.userLanguage, 'en']);
|
||||
});
|
||||
|
||||
html10n.bind('localized', () => {
|
||||
document.documentElement.lang = html10n.getLanguage();
|
||||
document.documentElement.dir = html10n.getDirection();
|
||||
});
|
||||
})(document);
|
18
src/static/js/l10n.ts
Normal file
18
src/static/js/l10n.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
import html10n from '../js/vendors/html10n';
|
||||
|
||||
|
||||
((document) => {
|
||||
// Set language for l10n
|
||||
let regexpLang: string | undefined;
|
||||
let language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/);
|
||||
if (language) regexpLang = language[1];
|
||||
|
||||
html10n.mt.bind('indexed', () => {
|
||||
html10n.localize([regexpLang, navigator.language, 'en']);
|
||||
});
|
||||
|
||||
html10n.mt.bind('localized', () => {
|
||||
document.documentElement.lang = html10n.getLanguage()!;
|
||||
document.documentElement.dir = html10n.getDirection()!;
|
||||
});
|
||||
})(document);
|
|
@ -30,6 +30,8 @@ require('./vendors/jquery');
|
|||
require('./vendors/farbtastic');
|
||||
require('./vendors/gritter');
|
||||
|
||||
import html10n from './vendors/html10n'
|
||||
|
||||
const Cookies = require('./pad_utils').Cookies;
|
||||
const chat = require('./chat').chat;
|
||||
const getCollabClient = require('./collab_client').getCollabClient;
|
||||
|
@ -136,7 +138,7 @@ const getParameters = [
|
|||
name: 'lang',
|
||||
checkVal: null,
|
||||
callback: (val) => {
|
||||
window.html10n.localize([val, 'en']);
|
||||
html10n.localize([val, 'en']);
|
||||
Cookies.set('language', val);
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
'use strict';
|
||||
import html10n from './vendors/html10n';
|
||||
|
||||
exports.showCountDownTimerToReconnectOnModal = ($modal, pad) => {
|
||||
if (clientVars.automaticReconnectionTimeout && $modal.is('.with_reconnect_timer')) {
|
||||
|
|
|
@ -25,6 +25,7 @@ const Cookies = require('./pad_utils').Cookies;
|
|||
const padcookie = require('./pad_cookie').padcookie;
|
||||
const padutils = require('./pad_utils').padutils;
|
||||
const Ace2Editor = require('./ace').Ace2Editor;
|
||||
import html10n from '../js/vendors/html10n'
|
||||
|
||||
const padeditor = (() => {
|
||||
let pad = undefined;
|
||||
|
@ -98,7 +99,7 @@ const padeditor = (() => {
|
|||
$('#languagemenu').val(html10n.getLanguage());
|
||||
$('#languagemenu').on('change', () => {
|
||||
Cookies.set('language', $('#languagemenu').val());
|
||||
window.html10n.localize([$('#languagemenu').val(), 'en']);
|
||||
html10n.localize([$('#languagemenu').val(), 'en']);
|
||||
if ($('select').niceSelect) {
|
||||
$('select').niceSelect('update');
|
||||
}
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import html10n from './vendors/html10n';
|
||||
|
||||
|
||||
const padimpexp = (() => {
|
||||
let pad;
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
const padutils = require('./pad_utils').padutils;
|
||||
const hooks = require('./pluginfw/hooks');
|
||||
|
||||
import html10n from './vendors/html10n';
|
||||
let myUserInfo = {};
|
||||
|
||||
let colorPickerOpen = false;
|
||||
|
|
1
src/static/js/vendors/farbtastic.js
vendored
1
src/static/js/vendors/farbtastic.js
vendored
|
@ -7,6 +7,7 @@
|
|||
// Licensed under the terms of the GNU General Public License v2.0:
|
||||
// https://github.com/mattfarina/farbtastic/blob/71ca15f4a09c8e5a08a1b0d1cf37ef028adf22f0/LICENSE.txt
|
||||
// edited by Sebastian Castro <sebastian.castro@protonmail.com> on 2020-04-06
|
||||
|
||||
(function ($) {
|
||||
|
||||
var __debug = false;
|
||||
|
|
1060
src/static/js/vendors/html10n.js
vendored
1060
src/static/js/vendors/html10n.js
vendored
File diff suppressed because it is too large
Load diff
992
src/static/js/vendors/html10n.ts
vendored
Normal file
992
src/static/js/vendors/html10n.ts
vendored
Normal file
|
@ -0,0 +1,992 @@
|
|||
import {Func} from "mocha";
|
||||
|
||||
|
||||
type PluralFunc = (n: number) => string
|
||||
|
||||
export class Html10n {
|
||||
public language?: string
|
||||
private rtl: string[]
|
||||
private _pluralRules?: PluralFunc
|
||||
public mt: MicroEvent
|
||||
private loader: Loader
|
||||
private translations: Map<string, any>
|
||||
private macros: Map<string, Function>
|
||||
|
||||
constructor() {
|
||||
this.language = undefined
|
||||
this.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"]
|
||||
this.mt = new MicroEvent()
|
||||
this.loader = new Loader([])
|
||||
this.translations = new Map()
|
||||
this.macros = new Map()
|
||||
|
||||
this.macros.set('plural', (_key: string, param:string, opts: any)=>{
|
||||
let str
|
||||
, n = parseFloat(param);
|
||||
if (isNaN(n))
|
||||
return;
|
||||
|
||||
// initialize _pluralRules
|
||||
if (this._pluralRules === undefined) {
|
||||
this._pluralRules = this.getPluralRules(this.language!);
|
||||
}
|
||||
let index = this._pluralRules!(n);
|
||||
|
||||
// try to find a [zero|one|two] key if it's defined
|
||||
if (n === 0 && ('zero') in opts) {
|
||||
str = opts['zero'];
|
||||
} else if (n == 1 && ('one') in opts) {
|
||||
str = opts['one'];
|
||||
} else if (n == 2 && ('two') in opts) {
|
||||
str = opts['two'];
|
||||
} else if (index in opts) {
|
||||
str = opts[index];
|
||||
}
|
||||
|
||||
return str;
|
||||
})
|
||||
|
||||
document.addEventListener('DOMContentLoaded', ()=> {
|
||||
this.index()
|
||||
}, false)
|
||||
}
|
||||
|
||||
bind(event: string, fct: Func) {
|
||||
this.mt.bind(event, fct)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rules for plural forms (shared with JetPack), see:
|
||||
* http://unicode.org/repos/cldr-tmp/trunk/diff/supplemental/language_plural_rules.html
|
||||
* https://github.com/mozilla/addon-sdk/blob/master/python-lib/plural-rules-generator.p
|
||||
*
|
||||
* @param {string} lang
|
||||
* locale (language) used.
|
||||
*
|
||||
* @return {PluralFunc}
|
||||
* returns a function that gives the plural form name for a given integer:
|
||||
* var fun = getPluralRules('en');
|
||||
* fun(1) -> 'one'
|
||||
* fun(0) -> 'other'
|
||||
* fun(1000) -> 'other'.
|
||||
*/
|
||||
getPluralRules(lang: string): PluralFunc {
|
||||
const locales2rules = new Map([
|
||||
['af', 3],
|
||||
['ak', 4],
|
||||
['am', 4],
|
||||
['ar', 1],
|
||||
['asa', 3],
|
||||
['az', 0],
|
||||
['be', 11],
|
||||
['bem', 3],
|
||||
['bez', 3],
|
||||
['bg', 3],
|
||||
['bh', 4],
|
||||
['bm', 0],
|
||||
['bn', 3],
|
||||
['bo', 0],
|
||||
['br', 20],
|
||||
['brx', 3],
|
||||
['bs', 11],
|
||||
['ca', 3],
|
||||
['cgg', 3],
|
||||
['chr', 3],
|
||||
['cs', 12],
|
||||
['cy', 17],
|
||||
['da', 3],
|
||||
['de', 3],
|
||||
['dv', 3],
|
||||
['dz', 0],
|
||||
['ee', 3],
|
||||
['el', 3],
|
||||
['en', 3],
|
||||
['eo', 3],
|
||||
['es', 3],
|
||||
['et', 3],
|
||||
['eu', 3],
|
||||
['fa', 0],
|
||||
['ff', 5],
|
||||
['fi', 3],
|
||||
['fil', 4],
|
||||
['fo', 3],
|
||||
['fr', 5],
|
||||
['fur', 3],
|
||||
['fy', 3],
|
||||
['ga', 8],
|
||||
['gd', 24],
|
||||
['gl', 3],
|
||||
['gsw', 3],
|
||||
['gu', 3],
|
||||
['guw', 4],
|
||||
['gv', 23],
|
||||
['ha', 3],
|
||||
['haw', 3],
|
||||
['he', 2],
|
||||
['hi', 4],
|
||||
['hr', 11],
|
||||
['hu', 0],
|
||||
['id', 0],
|
||||
['ig', 0],
|
||||
['ii', 0],
|
||||
['is', 3],
|
||||
['it', 3],
|
||||
['iu', 7],
|
||||
['ja', 0],
|
||||
['jmc', 3],
|
||||
['jv', 0],
|
||||
['ka', 0],
|
||||
['kab', 5],
|
||||
['kaj', 3],
|
||||
['kcg', 3],
|
||||
['kde', 0],
|
||||
['kea', 0],
|
||||
['kk', 3],
|
||||
['kl', 3],
|
||||
['km', 0],
|
||||
['kn', 0],
|
||||
['ko', 0],
|
||||
['ksb', 3],
|
||||
['ksh', 21],
|
||||
['ku', 3],
|
||||
['kw', 7],
|
||||
['lag', 18],
|
||||
['lb', 3],
|
||||
['lg', 3],
|
||||
['ln', 4],
|
||||
['lo', 0],
|
||||
['lt', 10],
|
||||
['lv', 6],
|
||||
['mas', 3],
|
||||
['mg', 4],
|
||||
['mk', 16],
|
||||
['ml', 3],
|
||||
['mn', 3],
|
||||
['mo', 9],
|
||||
['mr', 3],
|
||||
['ms', 0],
|
||||
['mt', 15],
|
||||
['my', 0],
|
||||
['nah', 3],
|
||||
['naq', 7],
|
||||
['nb', 3],
|
||||
['nd', 3],
|
||||
['ne', 3],
|
||||
['nl', 3],
|
||||
['nn', 3],
|
||||
['no', 3],
|
||||
['nr', 3],
|
||||
['nso', 4],
|
||||
['ny', 3],
|
||||
['nyn', 3],
|
||||
['om', 3],
|
||||
['or', 3],
|
||||
['pa', 3],
|
||||
['pap', 3],
|
||||
['pl', 13],
|
||||
['ps', 3],
|
||||
['pt', 3],
|
||||
['rm', 3],
|
||||
['ro', 9],
|
||||
['rof', 3],
|
||||
['ru', 11],
|
||||
['rwk', 3],
|
||||
['sah', 0],
|
||||
['saq', 3],
|
||||
['se', 7],
|
||||
['seh', 3],
|
||||
['ses', 0],
|
||||
['sg', 0],
|
||||
['sh', 11],
|
||||
['shi', 19],
|
||||
['sk', 12],
|
||||
['sl', 14],
|
||||
['sma', 7],
|
||||
['smi', 7],
|
||||
['smj', 7],
|
||||
['smn', 7],
|
||||
['sms', 7],
|
||||
['sn', 3],
|
||||
['so', 3],
|
||||
['sq', 3],
|
||||
['sr', 11],
|
||||
['ss', 3],
|
||||
['ssy', 3],
|
||||
['st', 3],
|
||||
['sv', 3],
|
||||
['sw', 3],
|
||||
['syr', 3],
|
||||
['ta', 3],
|
||||
['te', 3],
|
||||
['teo', 3],
|
||||
['th', 0],
|
||||
['ti', 4],
|
||||
['tig', 3],
|
||||
['tk', 3],
|
||||
['tl', 4],
|
||||
['tn', 3],
|
||||
['to', 0],
|
||||
['tr', 0],
|
||||
['ts', 3],
|
||||
['tzm', 22],
|
||||
['uk', 11],
|
||||
['ur', 3],
|
||||
['ve', 3],
|
||||
['vi', 0],
|
||||
['vun', 3],
|
||||
['wa', 4],
|
||||
['wae', 3],
|
||||
['wo', 0],
|
||||
['xh', 3],
|
||||
['xog', 3],
|
||||
['yo', 0],
|
||||
['zh', 0],
|
||||
['zu', 3]
|
||||
])
|
||||
|
||||
function isIn(n: number, list: number[]) {
|
||||
return list.indexOf(n) !== -1;
|
||||
}
|
||||
function isBetween(n: number, start: number, end: number) {
|
||||
return start <= n && n <= end;
|
||||
}
|
||||
|
||||
type PluralFunc = (n: number) => string
|
||||
|
||||
|
||||
const pluralRules: {
|
||||
[key: string]: PluralFunc
|
||||
} = {
|
||||
'0': function() {
|
||||
return 'other';
|
||||
},
|
||||
'1': function(n: number) {
|
||||
if ((isBetween((n % 100), 3, 10)))
|
||||
return 'few';
|
||||
if (n === 0)
|
||||
return 'zero';
|
||||
if ((isBetween((n % 100), 11, 99)))
|
||||
return 'many';
|
||||
if (n == 2)
|
||||
return 'two';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'2': function(n: number) {
|
||||
if (n !== 0 && (n % 10) === 0)
|
||||
return 'many';
|
||||
if (n == 2)
|
||||
return 'two';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'3': function(n: number) {
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'4': function(n: number) {
|
||||
if ((isBetween(n, 0, 1)))
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'5': function(n: number) {
|
||||
if ((isBetween(n, 0, 2)) && n != 2)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'6': function(n: number) {
|
||||
if (n === 0)
|
||||
return 'zero';
|
||||
if ((n % 10) == 1 && (n % 100) != 11)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'7': function(n: number) {
|
||||
if (n == 2)
|
||||
return 'two';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'8': function(n: number) {
|
||||
if ((isBetween(n, 3, 6)))
|
||||
return 'few';
|
||||
if ((isBetween(n, 7, 10)))
|
||||
return 'many';
|
||||
if (n == 2)
|
||||
return 'two';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'9': function(n: number) {
|
||||
if (n === 0 || n != 1 && (isBetween((n % 100), 1, 19)))
|
||||
return 'few';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'10': function(n: number) {
|
||||
if ((isBetween((n % 10), 2, 9)) && !(isBetween((n % 100), 11, 19)))
|
||||
return 'few';
|
||||
if ((n % 10) == 1 && !(isBetween((n % 100), 11, 19)))
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'11': function(n: number) {
|
||||
if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
|
||||
return 'few';
|
||||
if ((n % 10) === 0 ||
|
||||
(isBetween((n % 10), 5, 9)) ||
|
||||
(isBetween((n % 100), 11, 14)))
|
||||
return 'many';
|
||||
if ((n % 10) == 1 && (n % 100) != 11)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'12': function(n: number) {
|
||||
if ((isBetween(n, 2, 4)))
|
||||
return 'few';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'13': function(n: number) {
|
||||
if ((isBetween((n % 10), 2, 4)) && !(isBetween((n % 100), 12, 14)))
|
||||
return 'few';
|
||||
if (n != 1 && (isBetween((n % 10), 0, 1)) ||
|
||||
(isBetween((n % 10), 5, 9)) ||
|
||||
(isBetween((n % 100), 12, 14)))
|
||||
return 'many';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'14': function(n: number) {
|
||||
if ((isBetween((n % 100), 3, 4)))
|
||||
return 'few';
|
||||
if ((n % 100) == 2)
|
||||
return 'two';
|
||||
if ((n % 100) == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'15': function(n: number) {
|
||||
if (n === 0 || (isBetween((n % 100), 2, 10)))
|
||||
return 'few';
|
||||
if ((isBetween((n % 100), 11, 19)))
|
||||
return 'many';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'16': function(n: number) {
|
||||
if ((n % 10) == 1 && n != 11)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'17': function(n: number) {
|
||||
if (n == 3)
|
||||
return 'few';
|
||||
if (n === 0)
|
||||
return 'zero';
|
||||
if (n == 6)
|
||||
return 'many';
|
||||
if (n == 2)
|
||||
return 'two';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'18': function(n: number) {
|
||||
if (n === 0)
|
||||
return 'zero';
|
||||
if ((isBetween(n, 0, 2)) && n !== 0 && n != 2)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'19': function(n: number) {
|
||||
if ((isBetween(n, 2, 10)))
|
||||
return 'few';
|
||||
if ((isBetween(n, 0, 1)))
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'20': function(n: number) {
|
||||
if ((isBetween((n % 10), 3, 4) || ((n % 10) == 9)) && !(
|
||||
isBetween((n % 100), 10, 19) ||
|
||||
isBetween((n % 100), 70, 79) ||
|
||||
isBetween((n % 100), 90, 99)
|
||||
))
|
||||
return 'few';
|
||||
if ((n % 1000000) === 0 && n !== 0)
|
||||
return 'many';
|
||||
if ((n % 10) == 2 && !isIn((n % 100), [12, 72, 92]))
|
||||
return 'two';
|
||||
if ((n % 10) == 1 && !isIn((n % 100), [11, 71, 91]))
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'21': function(n: number) {
|
||||
if (n === 0)
|
||||
return 'zero';
|
||||
if (n == 1)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'22': function(n: number) {
|
||||
if ((isBetween(n, 0, 1)) || (isBetween(n, 11, 99)))
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'23': function(n: number) {
|
||||
if ((isBetween((n % 10), 1, 2)) || (n % 20) === 0)
|
||||
return 'one';
|
||||
return 'other';
|
||||
},
|
||||
'24': function(n: number) {
|
||||
if ((isBetween(n, 3, 10) || isBetween(n, 13, 19)))
|
||||
return 'few';
|
||||
if (isIn(n, [2, 12]))
|
||||
return 'two';
|
||||
if (isIn(n, [1, 11]))
|
||||
return 'one';
|
||||
return 'other';
|
||||
}
|
||||
};
|
||||
|
||||
const index = locales2rules.get(lang.replace(/-.*$/, ''));
|
||||
// @ts-ignore
|
||||
if (!(index in pluralRules)) {
|
||||
console.warn('plural form unknown for [' + lang + ']');
|
||||
return function() { return 'other'; };
|
||||
}
|
||||
// @ts-ignore
|
||||
return pluralRules[index];
|
||||
}
|
||||
|
||||
getTranslatableChildren(element: HTMLElement) {
|
||||
if(!document.querySelectorAll) {
|
||||
if (!element) return []
|
||||
const nodes = element.getElementsByTagName('*')
|
||||
, l10nElements = []
|
||||
for (let i=0, n=nodes.length; i < n; i++) {
|
||||
if (nodes[i].getAttribute('data-l10n-id'))
|
||||
l10nElements.push(nodes[i]);
|
||||
}
|
||||
return l10nElements
|
||||
}
|
||||
return element.querySelectorAll('*[data-l10n-id]')
|
||||
}
|
||||
|
||||
localize(langs: (string|undefined)[]|string) {
|
||||
if ('string' === typeof langs) {
|
||||
langs = [langs];
|
||||
}
|
||||
let i = 0
|
||||
langs.forEach((lang) => {
|
||||
if(!lang) return;
|
||||
langs[i++] = lang;
|
||||
if(~lang.indexOf('-')) langs[i++] = lang.substring(0, lang.indexOf('-'));
|
||||
})
|
||||
|
||||
this.build(langs, (er: null, translations: Map<string, any>) =>{
|
||||
this.translations = translations
|
||||
this.translateElement(translations)
|
||||
this.mt.trigger('localized')
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers the translation process
|
||||
* for an element
|
||||
* @param translations A hash of all translation strings
|
||||
* @param element A DOM element, if omitted, the document element will be used
|
||||
*/
|
||||
translateElement(translations: Map<string, any>, element?: HTMLElement) {
|
||||
element = element || document.documentElement
|
||||
const children = element ? this.getTranslatableChildren(element): document.childNodes
|
||||
|
||||
for (let child of children) {
|
||||
this.translateNode(translations, child as HTMLElement)
|
||||
}
|
||||
|
||||
// translate element itself if necessary
|
||||
this.translateNode(translations, element)
|
||||
}
|
||||
|
||||
asyncForEach(list: (string|undefined)[], iterator: any, cb: Function) {
|
||||
let i = 0
|
||||
, n = list.length
|
||||
iterator(list[i], i, function each(err?: string) {
|
||||
if(err) console.error(err)
|
||||
i++
|
||||
if (i < n) return iterator(list[i],i, each);
|
||||
cb()
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a translation object from a list of langs (loads the necessary translations)
|
||||
* @param langs Array - a list of langs sorted by priority (default langs should go last)
|
||||
* @param cb Function - a callback that will be called once all langs have been loaded
|
||||
*/
|
||||
build(langs: (string|undefined)[], cb: Function) {
|
||||
|
||||
const that = this;
|
||||
const build = new Map<string, any>()
|
||||
|
||||
this.asyncForEach(langs, function (lang: string, _i: number, next:LoaderFunc) {
|
||||
if(!lang) return next();
|
||||
that.loader.load(lang, next)
|
||||
}, () =>{
|
||||
let lang;
|
||||
langs.reverse()
|
||||
|
||||
// loop through the priority array...
|
||||
for (let i=0, n=langs.length; i < n; i++) {
|
||||
lang = langs[i]
|
||||
|
||||
if(!lang) continue;
|
||||
if(!(lang in that.loader.langs)) {// uh, we don't have this lang availbable..
|
||||
// then check for related langs
|
||||
if(~lang.indexOf('-')) lang = lang.split('-')[0];
|
||||
let l
|
||||
for(l in that.loader.langs) {
|
||||
if(lang != l && l.indexOf(lang) === 0) {
|
||||
lang = l
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(lang != l) continue;
|
||||
}
|
||||
|
||||
// ... and apply all strings of the current lang in the list
|
||||
// to our build object
|
||||
for (let string in that.loader.langs.get(lang)) {
|
||||
build.set(string,that.loader.langs.get(lang).get(string))
|
||||
}
|
||||
|
||||
// the last applied lang will be exposed as the
|
||||
// lang the page was translated to
|
||||
that.language = lang
|
||||
}
|
||||
cb(null, build)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the language that was last applied to the translations hash
|
||||
* thus overriding most of the formerly applied langs
|
||||
*/
|
||||
getLanguage() {
|
||||
return this.language
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the direction of the language returned be html10n#getLanguage
|
||||
*/
|
||||
getDirection() {
|
||||
if(!this.language) return
|
||||
const langCode = this.language.indexOf('-') == -1? this.language : this.language.substring(0, this.language.indexOf('-'))
|
||||
return this.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl'
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Index all <link>s
|
||||
*/
|
||||
index() {
|
||||
// Find all <link>s
|
||||
const links = document.getElementsByTagName('link')
|
||||
, resources = []
|
||||
for (let i=0, n=links.length; i < n; i++) {
|
||||
if (links[i].type != 'application/l10n+json')
|
||||
continue;
|
||||
resources.push(links[i].href)
|
||||
}
|
||||
this.loader = new Loader(resources)
|
||||
this.mt.trigger('indexed')
|
||||
}
|
||||
|
||||
translateNode(translations: Map<string, any>, node: HTMLElement) {
|
||||
const str: {
|
||||
id?: string,
|
||||
args?: any,
|
||||
str?: string
|
||||
|
||||
} = {}
|
||||
|
||||
// get id
|
||||
str.id = node.getAttribute('data-l10n-id') as string
|
||||
if (!str.id) return
|
||||
|
||||
if(!translations.get(str.id)) return console.warn('Couldn\'t find translation key '+str.id)
|
||||
|
||||
// get args
|
||||
if(window.JSON) {
|
||||
str.args = JSON.parse(node.getAttribute('data-l10n-args') as string)
|
||||
}else{
|
||||
try{
|
||||
str.args = eval(node.getAttribute('data-l10n-args') as string)
|
||||
}catch(e) {
|
||||
console.warn('Couldn\'t parse args for '+str.id)
|
||||
}
|
||||
}
|
||||
|
||||
str.str = this.get(str.id, str.args)
|
||||
|
||||
// get attribute name to apply str to
|
||||
let prop
|
||||
, index = str.id.lastIndexOf('.')
|
||||
, attrList = // allowed attributes
|
||||
{ "title": 1
|
||||
, "innerHTML": 1
|
||||
, "alt": 1
|
||||
, "textContent": 1
|
||||
, "value": 1
|
||||
, "placeholder": 1
|
||||
}
|
||||
if (index > 0 && str.id.substring(index + 1) in attrList) {
|
||||
// an attribute has been specified (example: "my_translation_key.placeholder")
|
||||
prop = str.id.substring(index + 1)
|
||||
} else { // no attribute: assuming text content by default
|
||||
prop = document.body.textContent ? 'textContent' : 'innerText'
|
||||
}
|
||||
|
||||
// Apply translation
|
||||
if (node.children.length === 0 || prop != 'textContent') {
|
||||
// @ts-ignore
|
||||
node[prop] = str.str!
|
||||
node.setAttribute("aria-label", str.str!); // Sets the aria-label
|
||||
// The idea of the above is that we always have an aria value
|
||||
// This might be a bit of an abrupt solution but let's see how it goes
|
||||
} else {
|
||||
let children = node.childNodes,
|
||||
found = false
|
||||
let i = 0, n = children.length;
|
||||
for (; i < n; i++) {
|
||||
if (children[i].nodeType === 3 && /\S/.test(children[i].textContent!)) {
|
||||
if (!found) {
|
||||
children[i].nodeValue = str.str!
|
||||
found = true
|
||||
} else {
|
||||
children[i].nodeValue = ''
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
console.warn('Unexpected error: could not translate element content for key '+str.id, node)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get(id: string, args?:any) {
|
||||
let translations = this.translations
|
||||
if(!translations) return console.warn('No translations available (yet)')
|
||||
if(!translations.get(id)) return console.warn('Could not find string '+id)
|
||||
|
||||
// apply macros
|
||||
let str = translations.get(id)
|
||||
|
||||
str = this.substMacros(id, str, args)
|
||||
|
||||
// apply args
|
||||
str = this.substArguments(str, args)
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
substMacros(key: string, str:string, args:any) {
|
||||
let regex = /\{\[\s*([a-zA-Z]+)\(([a-zA-Z]+)\)((\s*([a-zA-Z]+)\: ?([ a-zA-Z{}]+),?)+)*\s*\]\}/ //.exec('{[ plural(n) other: are {{n}}, one: is ]}')
|
||||
, match
|
||||
|
||||
while(match = regex.exec(str)) {
|
||||
// a macro has been found
|
||||
// Note: at the moment, only one parameter is supported
|
||||
let macroName = match[1]
|
||||
, paramName = match[2]
|
||||
, optv = match[3]
|
||||
, opts: {[key:string]:any} = {}
|
||||
|
||||
if (!(this.macros.has(macroName))) continue
|
||||
|
||||
if(optv) {
|
||||
optv.match(/(?=\s*)([a-zA-Z]+)\: ?([ a-zA-Z{}]+)(?=,?)/g)!.forEach(function(arg) {
|
||||
const parts = arg.split(':')
|
||||
, name = parts[0];
|
||||
opts[name] = parts[1].trim()
|
||||
})
|
||||
}
|
||||
|
||||
let param
|
||||
if (args && paramName in args) {
|
||||
param = args[paramName]
|
||||
} else if (paramName in this.translations) {
|
||||
param = this.translations.get(paramName)
|
||||
}
|
||||
|
||||
// there's no macro parser: it has to be defined in html10n.macros
|
||||
let macro = this.macros.get(macroName)!
|
||||
str = str.substring(0, match.index) + macro(key, param, opts) + str.substring(match.index+match[0].length)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
substArguments(str: string, args:any) {
|
||||
let reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/
|
||||
, match
|
||||
let translations = this.translations;
|
||||
while (match = reArgs.exec(str)) {
|
||||
if (!match || match.length < 2)
|
||||
return str // argument key not found
|
||||
|
||||
let arg = match[1]
|
||||
, sub = ''
|
||||
if (args && arg in args) {
|
||||
sub = args[arg]
|
||||
} else if (translations && arg in translations) {
|
||||
sub = translations.get(arg)
|
||||
} else {
|
||||
console.warn('Could not find argument {{' + arg + '}}')
|
||||
return str
|
||||
}
|
||||
|
||||
str = str.substring(0, match.index) + sub + str.substring(match.index + match[0].length)
|
||||
}
|
||||
|
||||
return str
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
class MicroEvent {
|
||||
private events: Map<string, Function[]>
|
||||
|
||||
constructor() {
|
||||
this.events = new Map();
|
||||
}
|
||||
|
||||
bind(event: string, fct: Func) {
|
||||
if (this.events.get(event) === undefined) {
|
||||
this.events.set(event, []);
|
||||
}
|
||||
|
||||
this.events.get(event)!.push(fct);
|
||||
}
|
||||
|
||||
unbind(event: string, fct: Func) {
|
||||
if (this.events.get(event) === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
const index = this.events.get(event)!.indexOf(fct);
|
||||
if (index !== -1) {
|
||||
this.events.get(event)!.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
trigger(event: string, ...args: any[]) {
|
||||
if (this.events.get(event) === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const fct of this.events.get(event)!) {
|
||||
fct(...args);
|
||||
}
|
||||
}
|
||||
|
||||
mixin(destObject: any) {
|
||||
const props = ['bind', 'unbind', 'trigger'];
|
||||
if (destObject !== undefined) {
|
||||
for (const prop of props) {
|
||||
// @ts-ignore
|
||||
destObject[prop] = this[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type LoaderFunc = () => void
|
||||
|
||||
type ErrorFunc = (data?:any)=>void
|
||||
|
||||
class Loader {
|
||||
private resources: any
|
||||
private cache: Map<string, any>
|
||||
langs: Map<string, any>
|
||||
|
||||
constructor(resources: any) {
|
||||
this.resources = resources;
|
||||
this.cache = new Map();
|
||||
this.langs = new Map();
|
||||
}
|
||||
|
||||
load(lang: string, callback: LoaderFunc) {
|
||||
if (this.langs.get(lang) !== undefined) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.resources.length > 0) {
|
||||
let reqs = 0
|
||||
for (const resource of this.resources) {
|
||||
this.fetch(resource, lang, (e)=> {
|
||||
reqs++;
|
||||
if (e) console.warn(e)
|
||||
|
||||
if (reqs < this.resources.length) return;// Call back once all reqs are completed
|
||||
callback && callback()
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetch(href: string, lang: string, callback: ErrorFunc) {
|
||||
const that = this;
|
||||
|
||||
if (this.cache.get(href)) {
|
||||
this.parse(lang, href, this.cache.get(href), callback)
|
||||
return;
|
||||
}
|
||||
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', href, /*async: */true)
|
||||
if (xhr.overrideMimeType) {
|
||||
xhr.overrideMimeType('application/json; charset=utf-8');
|
||||
}
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState == 4) {
|
||||
if (xhr.status == 200 || xhr.status === 0) {
|
||||
const data = JSON.parse(xhr.responseText);
|
||||
that.cache.set(href, data)
|
||||
// Pass on the contents for parsing
|
||||
that.parse(lang, href, data, callback)
|
||||
} else {
|
||||
callback(new Error('Failed to load '+href))
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send(null);
|
||||
}
|
||||
|
||||
parse(lang: string, href: string, data: {
|
||||
[key: string]: string
|
||||
}, callback: ErrorFunc) {
|
||||
if ('object' !== typeof data) {
|
||||
callback(new Error('A file couldn\'t be parsed as json.'))
|
||||
return
|
||||
}
|
||||
|
||||
function getBcp47LangCode(browserLang: string) {
|
||||
const bcp47Lang = browserLang.toLowerCase();
|
||||
|
||||
// Browser => BCP 47
|
||||
const langCodeMap = new Map([
|
||||
['zh-cn', 'zh-hans-cn'],
|
||||
['zh-hk', 'zh-hant-hk'],
|
||||
['zh-mo', 'zh-hant-mo'],
|
||||
['zh-my', 'zh-hans-my'],
|
||||
['zh-sg', 'zh-hans-sg'],
|
||||
['zh-tw', 'zh-hant-tw'],
|
||||
])
|
||||
|
||||
return langCodeMap.get(bcp47Lang) ?? bcp47Lang;
|
||||
}
|
||||
|
||||
// Issue #6129: Fix exceptions
|
||||
// NOTE: translatewiki.net use all lowercase form by default ('en-gb' insted of 'en-GB')
|
||||
function getJsonLangCode(bcp47Lang: string) {
|
||||
const jsonLang = bcp47Lang.toLowerCase();
|
||||
// BCP 47 => JSON
|
||||
const langCodeMap = new Map([
|
||||
['sr-ec', 'sr-cyrl'],
|
||||
['sr-el', 'sr-latn'],
|
||||
['zh-hk', 'zh-hant-hk'],
|
||||
])
|
||||
|
||||
return langCodeMap.get(jsonLang) ?? jsonLang;
|
||||
}
|
||||
|
||||
let bcp47LangCode = getBcp47LangCode(lang);
|
||||
let jsonLangCode = getJsonLangCode(bcp47LangCode);
|
||||
|
||||
if (!data[jsonLangCode]) {
|
||||
// lang not found
|
||||
// This may be due to formatting (expected 'ru' but browser sent 'ru-RU')
|
||||
// Set err msg before mutating lang (we may need this later)
|
||||
const msg = 'Couldn\'t find translations for ' + lang +
|
||||
'(lowercase BCP 47 lang tag ' + bcp47LangCode +
|
||||
', JSON lang code ' + jsonLangCode + ')';
|
||||
// Check for '-' (BCP 47 'ROOT-SCRIPT-REGION-VARIANT') and fallback until found data or ROOT
|
||||
// - 'ROOT-SCRIPT-REGION': 'zh-Hans-CN'
|
||||
// - 'ROOT-SCRIPT': 'zh-Hans'
|
||||
// - 'ROOT-REGION': 'en-GB'
|
||||
// - 'ROOT-VARIANT': 'be-tarask'
|
||||
while (!data[jsonLangCode] && bcp47LangCode.lastIndexOf('-') > -1) {
|
||||
// ROOT-SCRIPT-REGION-VARIANT formatting detected
|
||||
bcp47LangCode = bcp47LangCode.substring(0, bcp47LangCode.lastIndexOf('-')); // set lang to ROOT lang
|
||||
jsonLangCode = getJsonLangCode(bcp47LangCode);
|
||||
}
|
||||
|
||||
if (!data[jsonLangCode]) {
|
||||
// ROOT lang not found. (e.g 'zh')
|
||||
// Loop through langs data. Maybe we have a variant? e.g (zh-hans)
|
||||
let l; // langs item. Declare outside of loop
|
||||
|
||||
for (l in data) {
|
||||
// Is not ROOT?
|
||||
// And is variant of ROOT?
|
||||
// (NOTE: index of ROOT equals 0 would cause unexpected ISO 639-1 vs. 639-3 issues,
|
||||
// so append dash into query string)
|
||||
// And is known lang?
|
||||
if (bcp47LangCode != l && l.indexOf(lang + '-') === 0 && data[l]) {
|
||||
bcp47LangCode = l; // set lang to ROOT-SCRIPT (e.g 'zh-hans')
|
||||
jsonLangCode = getJsonLangCode(bcp47LangCode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Did we find a variant? If not, return err.
|
||||
if (bcp47LangCode != l) {
|
||||
return callback(new Error(msg));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
lang = jsonLangCode
|
||||
|
||||
if('string' === typeof data[lang]) {
|
||||
// Import rule
|
||||
|
||||
// absolute path
|
||||
let importUrl = data[lang];
|
||||
|
||||
// relative path
|
||||
if(data[lang].indexOf("http") != 0 && data[lang].indexOf("/") != 0) {
|
||||
importUrl = href+"/../"+data[lang]
|
||||
}
|
||||
|
||||
this.fetch(importUrl, lang, callback)
|
||||
return
|
||||
}
|
||||
|
||||
if ('object' != typeof data[lang]) {
|
||||
callback(new Error('Translations should be specified as JSON objects!'))
|
||||
return
|
||||
}
|
||||
|
||||
this.langs.set(lang,data[lang])
|
||||
// TODO: Also store accompanying langs
|
||||
callback()
|
||||
}
|
||||
}
|
||||
|
||||
export default new Html10n()
|
41
src/templates/padViteBootstrap.js
Normal file
41
src/templates/padViteBootstrap.js
Normal file
|
@ -0,0 +1,41 @@
|
|||
window.$ = window.jQuery = await import('../../src/static/js/rjquery').jQuery;
|
||||
await import('../../src/static/js/l10n')
|
||||
|
||||
window.clientVars = {
|
||||
// This is needed to fetch /pluginfw/plugin-definitions.json, which happens before the server
|
||||
// sends the CLIENT_VARS message.
|
||||
randomVersionString: "7a7bdbad",
|
||||
};
|
||||
|
||||
(async () => {
|
||||
// Allow other frames to access this frame's modules.
|
||||
//window.require.resolveTmp = require.resolve('ep_etherpad-lite/static/js/pad_cookie');
|
||||
|
||||
const basePath = new URL('..', window.location.href).pathname;
|
||||
window.browser = require('../../src/static/js/vendors/browser');
|
||||
const pad = require('../../src/static/js/pad');
|
||||
pad.baseURL = basePath;
|
||||
window.plugins = require('../../src/static/js/pluginfw/client_plugins');
|
||||
const hooks = require('../../src/static/js/pluginfw/hooks');
|
||||
|
||||
// TODO: These globals shouldn't exist.
|
||||
window.pad = pad.pad;
|
||||
window.chat = require('../../src/static/js/chat').chat;
|
||||
window.padeditbar = require('../../src/static/js/pad_editbar').padeditbar;
|
||||
window.padimpexp = require('../../src/static/js/pad_impexp').padimpexp;
|
||||
require('../../src/static/js/skin_variants');
|
||||
require('../../src/static/js/basic_error_handler')
|
||||
|
||||
window.plugins.baseURL = basePath;
|
||||
await window.plugins.update(new Map([
|
||||
|
||||
]));
|
||||
// Mechanism for tests to register hook functions (install fake plugins).
|
||||
window._postPluginUpdateForTestingDone = false;
|
||||
if (window._postPluginUpdateForTesting != null) window._postPluginUpdateForTesting();
|
||||
window._postPluginUpdateForTestingDone = true;
|
||||
window.pluginDefs = require('../../src/static/js/pluginfw/plugin_defs');
|
||||
pad.init();
|
||||
await new Promise((resolve) => $(resolve));
|
||||
await hooks.aCallAll('documentReady');
|
||||
})();
|
|
@ -9,7 +9,12 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"devDependencies": {
|
||||
"ep_etherpad-lite": "workspace:../src",
|
||||
"typescript": "^5.5.3",
|
||||
"vite": "^5.3.3"
|
||||
"vite": "^5.3.3",
|
||||
"vite-plugin-require": "^1.2.14"
|
||||
},
|
||||
"dependencies": {
|
||||
"@originjs/vite-plugin-commonjs": "^1.0.3"
|
||||
}
|
||||
}
|
||||
|
|
686
ui/pad.html
Normal file
686
ui/pad.html
Normal file
|
@ -0,0 +1,686 @@
|
|||
<!doctype html>
|
||||
<html translate="no" class="pad super-light-toolbar super-light-editor light-background">
|
||||
<head>
|
||||
|
||||
|
||||
<title>Etherpad</title>
|
||||
<link rel="manifest" href="/manifest.json" />
|
||||
<script>
|
||||
/*
|
||||
|@licstart The following is the entire license notice for the
|
||||
JavaScript code in this page.|
|
||||
|
||||
Copyright 2011 Peter Martischka, Primary Technology.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
||||
|@licend The above is the entire license notice
|
||||
for the JavaScript code in this page.|
|
||||
*/
|
||||
</script>
|
||||
|
||||
<meta charset="utf-8">
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="referrer" content="no-referrer">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
|
||||
<link rel="shortcut icon" href="../favicon.ico">
|
||||
|
||||
|
||||
<link href="../static/css/pad.css?v=5ba315cd" rel="stylesheet">
|
||||
|
||||
|
||||
<link href="../static/skins/colibris/pad.css?v=5ba315cd" rel="stylesheet">
|
||||
|
||||
|
||||
<style title="dynamicsyntax"></style>
|
||||
|
||||
|
||||
<link rel="localizations" type="application/l10n+json" href="../locales.json" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
||||
<!----------------------------->
|
||||
<!--------- TOOLBAR ----------->
|
||||
<!----------------------------->
|
||||
<div id="editbar" class="toolbar">
|
||||
<div id="toolbar-overlay"></div>
|
||||
|
||||
<ul class="menu_left" role="toolbar">
|
||||
|
||||
<li data-type="button" data-key="bold"><a class="grouped-left" data-l10n-id="pad.toolbar.bold.title"><button class=" buttonicon buttonicon-bold" data-l10n-id="pad.toolbar.bold.title"></button></a></li>
|
||||
<li data-type="button" data-key="italic"><a class="grouped-middle" data-l10n-id="pad.toolbar.italic.title"><button class=" buttonicon buttonicon-italic" data-l10n-id="pad.toolbar.italic.title"></button></a></li>
|
||||
<li data-type="button" data-key="underline"><a class="grouped-middle" data-l10n-id="pad.toolbar.underline.title"><button class=" buttonicon buttonicon-underline" data-l10n-id="pad.toolbar.underline.title"></button></a></li>
|
||||
<li data-type="button" data-key="strikethrough"><a class="grouped-right" data-l10n-id="pad.toolbar.strikethrough.title"><button class=" buttonicon buttonicon-strikethrough" data-l10n-id="pad.toolbar.strikethrough.title"></button
|
||||
></a></li><li class="separator"></li><li data-type="button" data-key="insertorderedlist"><a class="grouped-left" data-l10n-id="pad.toolbar.ol.title"><button class=" buttonicon buttonicon-insertorderedlist" data-l10n-id="pad.toolbar.ol.title"></button></a></li>
|
||||
<li data-type="button" data-key="insertunorderedlist"><a class="grouped-middle" data-l10n-id="pad.toolbar.ul.title"><button class=" buttonicon buttonicon-insertunorderedlist" data-l10n-id="pad.toolbar.ul.title"></button></a></li>
|
||||
<li data-type="button" data-key="indent"><a class="grouped-middle" data-l10n-id="pad.toolbar.indent.title"><button class=" buttonicon buttonicon-indent" data-l10n-id="pad.toolbar.indent.title"></button></a></li>
|
||||
<li data-type="button" data-key="outdent"><a class="grouped-right" data-l10n-id="pad.toolbar.unindent.title"><button class=" buttonicon buttonicon-outdent" data-l10n-id="pad.toolbar.unindent.title"></button></a></li><li class="separator"></li><li data-type="button" data-key="undo"><a class="grouped-left" data-l10n-id="pad.toolbar.undo.title"><button class=" buttonicon buttonicon-undo" data-l10n-id="pad.toolbar.undo.title"></button></a></li>
|
||||
<li data-type="button" data-key="redo"><a class="grouped-right" data-l10n-id="pad.toolbar.redo.title"><button class=" buttonicon buttonicon-redo" data-l10n-id="pad.toolbar.redo.title"></button></a></li><li class="separator"></li
|
||||
><li data-type="button" data-key="clearauthorship"><a class="" data-l10n-id="pad.toolbar.clearAuthorship.title"><button class=" buttonicon buttonicon-clearauthorship" data-l10n-id="pad.toolbar.clearAuthorship.title"></button></a></li>
|
||||
|
||||
</ul>
|
||||
<ul class="menu_right" role="toolbar">
|
||||
|
||||
<li data-type="button" data-key="import_export"><a class="grouped-left" data-l10n-id="pad.toolbar.import_export.title"><button class=" buttonicon buttonicon-import_export" data-l10n-id="pad.toolbar.import_export.title"></button></a></li>
|
||||
<li data-type="button" data-key="showTimeSlider"><a class="grouped-middle" data-l10n-id="pad.toolbar.timeslider.title"><button class=" buttonicon buttonicon-history" data-l10n-id="pad.toolbar.timeslider.title"></button></a></li>
|
||||
<li data-type="button" data-key="savedRevision"><a class="grouped-right" data-l10n-id="pad.toolbar.savedRevision.title"><button class=" buttonicon buttonicon-savedRevision" data-l10n-id="pad.toolbar.savedRevision.title"></button
|
||||
></a></li><li class="separator"></li><li data-type="button" data-key="settings"><a class="grouped-left" data-l10n-id="pad.toolbar.settings.title"><button class=" buttonicon buttonicon-settings" data-l10n-id="pad.toolbar.settings.title"></button></a></li>
|
||||
<li data-type="button" data-key="embed"><a class="grouped-right" data-l10n-id="pad.toolbar.embed.title"><button class=" buttonicon buttonicon-embed" data-l10n-id="pad.toolbar.embed.title"></button></a></li><li class="separator"></li><li data-type="button" data-key="showusers"><a class="" data-l10n-id="pad.toolbar.showusers.title"><button class=" buttonicon buttonicon-showusers" data-l10n-id="pad.toolbar.showusers.title"></button></a></li>
|
||||
|
||||
</ul>
|
||||
<span class="show-more-icon-btn"></span> <!-- use on small screen to display hidden toolbar buttons -->
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
<div id="editorcontainerbox" class="flex-layout">
|
||||
|
||||
|
||||
|
||||
<!----------------------------->
|
||||
<!--- PAD EDITOR (in iframe) -->
|
||||
<!----------------------------->
|
||||
|
||||
<div id="editorcontainer" class="editorcontainer"></div>
|
||||
|
||||
<div id="editorloadingbox">
|
||||
|
||||
<div id="permissionDenied">
|
||||
<p data-l10n-id="pad.permissionDenied" class="editorloadingbox-message">
|
||||
You do not have permission to access this pad
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
<p data-l10n-id="pad.loading" id="loading" class="editorloadingbox-message">
|
||||
<img src='../static/img/brand.svg' class='etherpadBrand'><br/>
|
||||
Loading...
|
||||
</p>
|
||||
|
||||
<noscript>
|
||||
<p class="editorloadingbox-message">
|
||||
<strong>
|
||||
Sorry, you have to enable Javascript in order to use this.
|
||||
</strong>
|
||||
</p>
|
||||
</noscript>
|
||||
</div>
|
||||
|
||||
|
||||
<!------------------------------------------------------------->
|
||||
<!-- SETTINGS POPUP (change font, language, chat parameters) -->
|
||||
<!------------------------------------------------------------->
|
||||
|
||||
<div id="settings" class="popup"><div class="popup-content">
|
||||
<h1 data-l10n-id="pad.settings.padSettings"></h1>
|
||||
|
||||
<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>
|
||||
|
||||
|
||||
<div class="dropdowns-container">
|
||||
|
||||
<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>
|
||||
Quicksand,Roboto,Alegreya,PlayfairDisplay,Montserrat,OpenDyslexic,RobotoMono
|
||||
|
||||
<option value="Quicksand">Quicksand</option>
|
||||
|
||||
<option value="Roboto">Roboto</option>
|
||||
|
||||
<option value="Alegreya">Alegreya</option>
|
||||
|
||||
<option value="PlayfairDisplay">PlayfairDisplay</option>
|
||||
|
||||
<option value="Montserrat">Montserrat</option>
|
||||
|
||||
<option value="OpenDyslexic">OpenDyslexic</option>
|
||||
|
||||
<option value="RobotoMono">RobotoMono</option>
|
||||
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p class="dropdown-line">
|
||||
<label for="languagemenu" data-l10n-id="pad.settings.language">Language:</label>
|
||||
<select id="languagemenu">
|
||||
|
||||
<option value="af">Afrikaans</option>
|
||||
|
||||
<option value="ar">العربية</option>
|
||||
|
||||
<option value="ast">asturianu</option>
|
||||
|
||||
<option value="az">azərbaycanca</option>
|
||||
|
||||
<option value="azb">تورکجه</option>
|
||||
|
||||
<option value="bcc">بلوچی مکرانی</option>
|
||||
|
||||
<option value="be-tarask">беларуская (тарашкевіца)</option>
|
||||
|
||||
<option value="bg">български</option>
|
||||
|
||||
<option value="bn">বাংলা</option>
|
||||
|
||||
<option value="br">brezhoneg</option>
|
||||
|
||||
<option value="bs">bosanski</option>
|
||||
|
||||
<option value="ca">català</option>
|
||||
|
||||
<option value="cs">česky</option>
|
||||
|
||||
<option value="da">dansk</option>
|
||||
|
||||
<option value="de">Deutsch</option>
|
||||
|
||||
<option value="diq">Zazaki</option>
|
||||
|
||||
<option value="dsb">dolnoserbski</option>
|
||||
|
||||
<option value="el">Ελληνικά</option>
|
||||
|
||||
<option value="en-gb">British English</option>
|
||||
|
||||
<option value="en">English</option>
|
||||
|
||||
<option value="eo">Esperanto</option>
|
||||
|
||||
<option value="es">español</option>
|
||||
|
||||
<option value="et">eesti</option>
|
||||
|
||||
<option value="eu">euskara</option>
|
||||
|
||||
<option value="fa">فارسی</option>
|
||||
|
||||
<option value="ff">Fulfulde</option>
|
||||
|
||||
<option value="fi">suomi</option>
|
||||
|
||||
<option value="fo">føroyskt</option>
|
||||
|
||||
<option value="fr">français</option>
|
||||
|
||||
<option value="fy">Frysk</option>
|
||||
|
||||
<option value="gl">galego</option>
|
||||
|
||||
<option value="gu">ગુજરાતી</option>
|
||||
|
||||
<option value="he">עברית</option>
|
||||
|
||||
<option value="hi">हिन्दी</option>
|
||||
|
||||
<option value="hr">hrvatski</option>
|
||||
|
||||
<option value="hsb">hornjoserbsce</option>
|
||||
|
||||
<option value="hu">magyar</option>
|
||||
|
||||
<option value="hy">Հայերեն</option>
|
||||
|
||||
<option value="ia">interlingua</option>
|
||||
|
||||
<option value="id">Bahasa Indonesia</option>
|
||||
|
||||
<option value="is">íslenska</option>
|
||||
|
||||
<option value="it">italiano</option>
|
||||
|
||||
<option value="ja">日本語</option>
|
||||
|
||||
<option value="kab">Taqbaylit</option>
|
||||
|
||||
<option value="km">ភាសាខ្មែរ</option>
|
||||
|
||||
<option value="kn">ಕನ್ನಡ</option>
|
||||
|
||||
<option value="ko">한국어</option>
|
||||
|
||||
<option value="krc">къарачай-малкъар</option>
|
||||
|
||||
<option value="ksh">Ripoarisch</option>
|
||||
|
||||
<option value="ku-latn">Kurdî (latînî)</option>
|
||||
|
||||
<option value="lb">Lëtzebuergesch</option>
|
||||
|
||||
<option value="lt">lietuvių</option>
|
||||
|
||||
<option value="lv">latviešu</option>
|
||||
|
||||
<option value="map-bms">Basa Banyumasan</option>
|
||||
|
||||
<option value="mg">Malagasy</option>
|
||||
|
||||
<option value="mk">македонски</option>
|
||||
|
||||
<option value="ml">മലയാളം</option>
|
||||
|
||||
<option value="mn">монгол</option>
|
||||
|
||||
<option value="mnw">ဘာသာ မန်</option>
|
||||
|
||||
<option value="mr">मराठी</option>
|
||||
|
||||
<option value="ms">Bahasa Melayu</option>
|
||||
|
||||
<option value="my">မြန်မာဘာသာ</option>
|
||||
|
||||
<option value="nah">Nāhuatl</option>
|
||||
|
||||
<option value="nap">Nnapulitano</option>
|
||||
|
||||
<option value="nb">norsk (bokmål)</option>
|
||||
|
||||
<option value="nds">Plattdüütsch</option>
|
||||
|
||||
<option value="ne">नेपाली</option>
|
||||
|
||||
<option value="nl">Nederlands</option>
|
||||
|
||||
<option value="nn">norsk (nynorsk)</option>
|
||||
|
||||
<option value="oc">occitan</option>
|
||||
|
||||
<option value="os">Ирон</option>
|
||||
|
||||
<option value="pa">ਪੰਜਾਬੀ</option>
|
||||
|
||||
<option value="pl">polski</option>
|
||||
|
||||
<option value="pms">Piemontèis</option>
|
||||
|
||||
<option value="ps">پښتو</option>
|
||||
|
||||
<option value="pt-br">português do Brasil</option>
|
||||
|
||||
<option value="pt">português</option>
|
||||
|
||||
<option value="qqq">Message documentation</option>
|
||||
|
||||
<option value="ro">română</option>
|
||||
|
||||
<option value="ru">русский</option>
|
||||
|
||||
<option value="sc">sardu</option>
|
||||
|
||||
<option value="sco">Scots</option>
|
||||
|
||||
<option value="sd">سنڌي</option>
|
||||
|
||||
<option value="sh">srpskohrvatski / српскохрватски</option>
|
||||
|
||||
<option value="shn">လိၵ်ႈတႆး</option>
|
||||
|
||||
<option value="sk">slovenčina</option>
|
||||
|
||||
<option value="sl">slovenščina</option>
|
||||
|
||||
<option value="sq">shqip</option>
|
||||
|
||||
<option value="sr-ec">српски (ћирилица)</option>
|
||||
|
||||
<option value="sr-el">srpski (latinica)</option>
|
||||
|
||||
<option value="sv">svenska</option>
|
||||
|
||||
<option value="sw">Kiswahili</option>
|
||||
|
||||
<option value="ta">தமிழ்</option>
|
||||
|
||||
<option value="tcy">ತುಳು</option>
|
||||
|
||||
<option value="te">తెలుగు</option>
|
||||
|
||||
<option value="th">ไทย</option>
|
||||
|
||||
<option value="tr">Türkçe</option>
|
||||
|
||||
<option value="uk">українська</option>
|
||||
|
||||
<option value="vec">vèneto</option>
|
||||
|
||||
<option value="vi">Tiếng Việt</option>
|
||||
|
||||
<option value="zh-hans">中文(简体)</option>
|
||||
|
||||
<option value="zh-hant">中文(繁體)</option>
|
||||
|
||||
</select>
|
||||
</p>
|
||||
|
||||
</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">Etherpad</a>
|
||||
|
||||
</div></div>
|
||||
|
||||
|
||||
<!------------------------->
|
||||
<!-- IMPORT EXPORT POPUP -->
|
||||
<!------------------------->
|
||||
|
||||
<div id="import_export" class="popup"><div class="popup-content">
|
||||
<h1 data-l10n-id="pad.importExport.import_export"></h1>
|
||||
<div class="acl-write">
|
||||
|
||||
<h2 data-l10n-id="pad.importExport.import"></h2>
|
||||
<div class="importmessage" id="importmessageabiword" data-l10n-id="pad.importExport.abiword.innerHTML"></div><br>
|
||||
<form id="importform" method="post" action="" target="importiframe" enctype="multipart/form-data">
|
||||
<div class="importformdiv" id="importformfilediv">
|
||||
<input type="file" name="file" size="10" id="importfileinput">
|
||||
<div class="importmessage" id="importmessagefail"></div>
|
||||
</div>
|
||||
<div id="import"></div>
|
||||
<div class="importmessage" id="importmessagesuccess" data-l10n-id="pad.importExport.importSuccessful"></div>
|
||||
<div class="importformdiv" id="importformsubmitdiv">
|
||||
<span class="nowrap">
|
||||
<input type="submit" class="btn btn-primary" name="submit" value="Import Now" disabled="disabled" id="importsubmitinput">
|
||||
<div alt="" id="importstatusball" class="loadingAnimation" align="top"></div>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<div id="exportColumn">
|
||||
<h2 data-l10n-id="pad.importExport.export"></h2>
|
||||
|
||||
<a id="exportetherpada" target="_blank" class="exportlink">
|
||||
<span class="exporttype buttonicon buttonicon-file-powerpoint" id="exportetherpad" data-l10n-id="pad.importExport.exportetherpad"></span>
|
||||
</a>
|
||||
<a id="exporthtmla" target="_blank" class="exportlink">
|
||||
<span class="exporttype buttonicon buttonicon-file-code" id="exporthtml" data-l10n-id="pad.importExport.exporthtml"></span>
|
||||
</a>
|
||||
<a id="exportplaina" target="_blank" class="exportlink">
|
||||
<span class="exporttype buttonicon buttonicon-file" id="exportplain" data-l10n-id="pad.importExport.exportplain"></span>
|
||||
</a>
|
||||
<a id="exportworda" target="_blank" class="exportlink">
|
||||
<span class="exporttype buttonicon buttonicon-file-word" id="exportword" data-l10n-id="pad.importExport.exportword"></span>
|
||||
</a>
|
||||
<a id="exportpdfa" target="_blank" class="exportlink">
|
||||
<span class="exporttype buttonicon buttonicon-file-pdf" id="exportpdf" data-l10n-id="pad.importExport.exportpdf"></span>
|
||||
</a>
|
||||
<a id="exportopena" target="_blank" class="exportlink">
|
||||
<span class="exporttype buttonicon buttonicon-file-alt" id="exportopen" data-l10n-id="pad.importExport.exportopen"></span>
|
||||
</a>
|
||||
|
||||
</div>
|
||||
</div></div>
|
||||
|
||||
|
||||
<!---------------------------------------------------->
|
||||
<!-- CONNECTIVITY POPUP (when you get disconnected) -->
|
||||
<!---------------------------------------------------->
|
||||
|
||||
<div id="connectivity" class="popup"><div class="popup-content">
|
||||
|
||||
<div class="connected visible">
|
||||
<h2 data-l10n-id="pad.modals.connected"></h2>
|
||||
</div>
|
||||
<div class="reconnecting">
|
||||
<h1 data-l10n-id="pad.modals.reconnecting"></h1>
|
||||
<i class='buttonicon buttonicon-spin5 icon-spin'>
|
||||
<img src='../static/img/brand.svg' class='etherpadBrand'><br/>
|
||||
</i>
|
||||
</div>
|
||||
<div class="userdup">
|
||||
<h1 data-l10n-id="pad.modals.userdup"></h1>
|
||||
<h2 data-l10n-id="pad.modals.userdup.explanation"></h2>
|
||||
<p id="defaulttext" data-l10n-id="pad.modals.userdup.advice"></p>
|
||||
<button id="forcereconnect" class="btn btn-primary" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
</div>
|
||||
<div class="unauth">
|
||||
<h1 data-l10n-id="pad.modals.unauth"></h1>
|
||||
<p id="defaulttext" data-l10n-id="pad.modals.unauth.explanation"></p>
|
||||
<button id="forcereconnect" class="btn btn-primary" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
</div>
|
||||
<div class="looping">
|
||||
<h1 data-l10n-id="pad.modals.disconnected"></h1>
|
||||
<h2 data-l10n-id="pad.modals.looping.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.looping.cause"></p>
|
||||
</div>
|
||||
<div class="initsocketfail">
|
||||
<h1 data-l10n-id="pad.modals.initsocketfail"></h1>
|
||||
<h2 data-l10n-id="pad.modals.initsocketfail.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.initsocketfail.cause"></p>
|
||||
</div>
|
||||
<div class="slowcommit with_reconnect_timer">
|
||||
<h1 data-l10n-id="pad.modals.disconnected"></h1>
|
||||
<h2 data-l10n-id="pad.modals.slowcommit.explanation"></h2>
|
||||
<p id="defaulttext" data-l10n-id="pad.modals.slowcommit.cause"></p>
|
||||
<button id="forcereconnect" class="btn btn-primary" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
</div>
|
||||
<div class="badChangeset with_reconnect_timer">
|
||||
<h1 data-l10n-id="pad.modals.disconnected"></h1>
|
||||
<h2 data-l10n-id="pad.modals.badChangeset.explanation"></h2>
|
||||
<p id="defaulttext" data-l10n-id="pad.modals.badChangeset.cause"></p>
|
||||
<button id="forcereconnect" class="btn btn-primary" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
</div>
|
||||
<div class="corruptPad">
|
||||
<h1 data-l10n-id="pad.modals.disconnected"></h1>
|
||||
<h2 data-l10n-id="pad.modals.corruptPad.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.corruptPad.cause"></p>
|
||||
</div>
|
||||
<div class="deleted">
|
||||
<h1 data-l10n-id="pad.modals.deleted"></h1>
|
||||
<p data-l10n-id="pad.modals.deleted.explanation"></p>
|
||||
</div>
|
||||
<div class="rateLimited">
|
||||
<h1 data-l10n-id="pad.modals.rateLimited"></h1>
|
||||
<p data-l10n-id="pad.modals.rateLimited.explanation"></p>
|
||||
</div>
|
||||
<div class="rejected">
|
||||
<h1 data-l10n-id="pad.modals.disconnected"></h1>
|
||||
<h2 data-l10n-id="pad.modals.rejected.explanation"></h2>
|
||||
<p data-l10n-id="pad.modals.rejected.cause"></p>
|
||||
</div>
|
||||
<div class="disconnected with_reconnect_timer">
|
||||
|
||||
<h1 data-l10n-id="pad.modals.disconnected"></h1>
|
||||
<h2 data-l10n-id="pad.modals.disconnected.explanation"></h2>
|
||||
<p id="defaulttext" data-l10n-id="pad.modals.disconnected.cause"></p>
|
||||
<button id="forcereconnect" class="btn btn-primary" data-l10n-id="pad.modals.forcereconnect"></button>
|
||||
|
||||
</div>
|
||||
<form id="reconnectform" method="post" action="/ep/pad/reconnect" accept-charset="UTF-8" style="display: none;">
|
||||
<input type="hidden" class="padId" name="padId">
|
||||
<input type="hidden" class="diagnosticInfo" name="diagnosticInfo">
|
||||
<input type="hidden" class="missedChanges" name="missedChanges">
|
||||
</form>
|
||||
|
||||
</div></div>
|
||||
|
||||
|
||||
<!-------------------------------->
|
||||
<!-- EMBED POPUP (Share, embed) -->
|
||||
<!-------------------------------->
|
||||
|
||||
<div id="embed" class="popup"><div class="popup-content">
|
||||
|
||||
<h1 data-l10n-id="pad.share"></h1>
|
||||
<div id="embedreadonly" class="acl-write">
|
||||
<input type="checkbox" id="readonlyinput">
|
||||
<label for="readonlyinput" data-l10n-id="pad.share.readonly"></label>
|
||||
</div>
|
||||
<div id="linkcode">
|
||||
<h2 data-l10n-id="pad.share.link"></h2>
|
||||
<input id="linkinput" type="text" value="" onclick="this.select()">
|
||||
</div>
|
||||
<div id="embedcode">
|
||||
<h2 data-l10n-id="pad.share.emebdcode"></h2>
|
||||
<input id="embedinput" type="text" value="" onclick="this.select()">
|
||||
</div>
|
||||
|
||||
</div></div>
|
||||
|
||||
<div class="sticky-container">
|
||||
|
||||
<!---------------------------------------------------------------------->
|
||||
<!-- USERS POPUP (set username, color, see other users names & color) -->
|
||||
<!---------------------------------------------------------------------->
|
||||
|
||||
<div id="users" class="popup"><div class="popup-content">
|
||||
|
||||
<div id="connectionstatus"></div>
|
||||
<div id="myuser">
|
||||
<div id="mycolorpicker" class="popup"><div class="popup-content">
|
||||
<div id="colorpicker"></div>
|
||||
<div class="btn-container">
|
||||
<button id="mycolorpickersave" data-l10n-id="pad.colorpicker.save" class="btn btn-primary"></button>
|
||||
<button id="mycolorpickercancel" data-l10n-id="pad.colorpicker.cancel" class="btn btn-default"></button>
|
||||
<span id="mycolorpickerpreview" class="myswatchboxhoverable"></span>
|
||||
</div>
|
||||
</div></div>
|
||||
<div id="myswatchbox"><div id="myswatch"></div></div>
|
||||
<div id="myusernameform">
|
||||
<input type="text" id="myusernameedit" disabled="disabled" data-l10n-id="pad.userlist.entername">
|
||||
</div>
|
||||
</div>
|
||||
<div id="otherusers" aria-role="document">
|
||||
<table id="otheruserstable" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr><td></td></tr>
|
||||
</table>
|
||||
</div>
|
||||
<div id="userlistbuttonarea"></div>
|
||||
|
||||
</div></div>
|
||||
|
||||
|
||||
<!----------------------------->
|
||||
<!----------- CHAT ------------>
|
||||
<!----------------------------->
|
||||
|
||||
<div id="chaticon" class="visible" onclick="chat.show();return false;" title="Chat (Alt C)">
|
||||
<span id="chatlabel" data-l10n-id="pad.chat"></span>
|
||||
<span class="buttonicon buttonicon-chat"></span>
|
||||
<span id="chatcounter">0</span>
|
||||
</div>
|
||||
|
||||
<div id="chatbox">
|
||||
<div class="chat-content">
|
||||
<div id="titlebar">
|
||||
<h1 id ="titlelabel" data-l10n-id="pad.chat"></h1>
|
||||
<a id="titlecross" class="hide-reduce-btn" onClick="chat.hide();return false;">- </a>
|
||||
<a id="titlesticky" class="stick-to-screen-btn" onClick="chat.stickToScreen(true);return false;" data-l10n-id="pad.chat.stick.title">█ </a>
|
||||
</div>
|
||||
<div id="chattext" class="thin-scrollbar" aria-live="polite" aria-relevant="additions removals text" role="log" aria-atomic="false">
|
||||
<div alt="loading.." id="chatloadmessagesball" class="chatloadmessages loadingAnimation" align="top"></div>
|
||||
<button id="chatloadmessagesbutton" class="chatloadmessages" data-l10n-id="pad.chat.loadmessages"></button>
|
||||
</div>
|
||||
<div id="chatinputbox">
|
||||
<form>
|
||||
<textarea id="chatinput" maxlength="999" data-l10n-id="pad.chat.writeMessage.placeholder"></textarea>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!------------------------------------------------------------------>
|
||||
<!-- SKIN VARIANTS BUILDER (Customize rendering, only for admins) -->
|
||||
<!------------------------------------------------------------------>
|
||||
|
||||
<div id="skin-variants" class="popup"><div class="popup-content">
|
||||
<h1>Skin Builder</h1>
|
||||
|
||||
<div class="dropdowns-container">
|
||||
|
||||
|
||||
<p class="dropdown-line">
|
||||
<label class="skin-variant-container">toolbar</label>
|
||||
<select class="skin-variant skin-variant-color" data-container="toolbar">
|
||||
<option value="super-light">Super Light</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="super-dark">Super Dark</option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p class="dropdown-line">
|
||||
<label class="skin-variant-container">background</label>
|
||||
<select class="skin-variant skin-variant-color" data-container="background">
|
||||
<option value="super-light">Super Light</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="super-dark">Super Dark</option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
<p class="dropdown-line">
|
||||
<label class="skin-variant-container">editor</label>
|
||||
<select class="skin-variant skin-variant-color" data-container="editor">
|
||||
<option value="super-light">Super Light</option>
|
||||
<option value="light">Light</option>
|
||||
<option value="dark">Dark</option>
|
||||
<option value="super-dark">Super Dark</option>
|
||||
</select>
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<p>
|
||||
<input type="checkbox" id="skin-variant-full-width" class="skin-variant"/>
|
||||
<label for="skin-variant-full-width">Full Width Editor</label>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<label>Result to copy in settings.json</label>
|
||||
<input id="skin-variants-result" type="text" readonly class="disabled" />
|
||||
</p>
|
||||
</div></div>
|
||||
|
||||
|
||||
|
||||
|
||||
</div> <!-- End of #editorcontainerbox -->
|
||||
|
||||
|
||||
|
||||
|
||||
<!----------------------------->
|
||||
<!-------- JAVASCRIPT --------->
|
||||
<!----------------------------->
|
||||
|
||||
<script type="text/javascript" src="../static/skins/colibris/pad.js?v=5ba315cd"></script>
|
||||
|
||||
<div style="display:none"><a href="/javascript" data-jslicense="1">JavaScript license information</a></div>
|
||||
<script type="module" src="./node_modules/ep_etherpad-lite/templates/padViteBootstrap.js"></script>
|
||||
</body>
|
||||
</html>
|
|
@ -1,10 +1,18 @@
|
|||
// vite.config.js
|
||||
import { resolve } from 'path'
|
||||
import { defineConfig } from 'vite'
|
||||
import vitePluginRequire from 'vite-plugin-require';
|
||||
import { viteCommonjs } from '@originjs/vite-plugin-commonjs'
|
||||
|
||||
export default defineConfig({
|
||||
base: '/views/',
|
||||
base: '/views/',
|
||||
plugins: [
|
||||
viteCommonjs(),
|
||||
],
|
||||
build: {
|
||||
commonjsOptions:{
|
||||
transformMixedEsModules: true,
|
||||
},
|
||||
outDir: resolve(__dirname, '../src/static/oidc'),
|
||||
rollupOptions: {
|
||||
input: {
|
||||
|
@ -14,4 +22,31 @@ export default defineConfig({
|
|||
},
|
||||
emptyOutDir: true,
|
||||
},
|
||||
server:{
|
||||
proxy:{
|
||||
'/static':{
|
||||
target: 'http://localhost:9001',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
},
|
||||
'/views/manifest.json':{
|
||||
target: 'http://localhost:9001',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
rewrite: (path) => path.replace(/^\/views/, ''),
|
||||
},
|
||||
'/locales.json':{
|
||||
target: 'http://localhost:9001',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
rewrite: (path) => path.replace(/^\/views/, ''),
|
||||
},
|
||||
'/locales':{
|
||||
target: 'http://localhost:9001',
|
||||
changeOrigin: true,
|
||||
secure: false,
|
||||
rewrite: (path) => path.replace(/^\/views/, ''),
|
||||
},
|
||||
}
|
||||
}
|
||||
})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue