refactor: initial import from v3 prototype

pull/5698/head
NGPixel 3 years ago
parent b5b4b0880a
commit 3a57145e3d
No known key found for this signature in database
GPG Key ID: 8FDA2F1757F60D63

@ -2,7 +2,7 @@
# Wiki.js - CONFIGURATION #
#######################################################################
# Full documentation + examples:
# https://docs.requarks.io/install
# https://docs.js.wiki/install
# ---------------------------------------------------------------------
# Port the server should listen to
@ -13,25 +13,20 @@ port: 3000
# ---------------------------------------------------------------------
# Database
# ---------------------------------------------------------------------
# Supported Database Engines:
# - postgres = PostgreSQL 9.5 or later
# - mysql = MySQL 8.0 or later (5.7.8 partially supported, refer to docs)
# - mariadb = MariaDB 10.2.7 or later
# - mssql = MS SQL Server 2012 or later
# - sqlite = SQLite 3.9 or later
# PostgreSQL 9.6 or later required
db:
type: postgres
# PostgreSQL / MySQL / MariaDB / MS SQL Server only:
host: localhost
port: 5432
user: wikijs
pass: wikijsrocks
db: wiki
schemas:
wiki: wiki
scheduler: scheduler
ssl: false
# Optional - PostgreSQL / MySQL / MariaDB only:
# Optional
# -> Uncomment lines you need below and set `auto` to false
# -> Full list of accepted options: https://nodejs.org/api/tls.html#tls_tls_createsecurecontext_options
sslOptions:
@ -43,12 +38,6 @@ db:
# pfx: path/to/cert.pfx
# passphrase: xyz123
# Optional - PostgreSQL only:
schema: public
# SQLite only:
storage: path/to/database.sqlite
#######################################################################
# ADVANCED OPTIONS #
#######################################################################
@ -102,15 +91,12 @@ pool:
bindIP: 0.0.0.0
# ---------------------------------------------------------------------
# Log Level
# Logging
# ---------------------------------------------------------------------
# Possible values: error, warn, info (default), verbose, debug, silly
logLevel: info
# ---------------------------------------------------------------------
# Log Format
# ---------------------------------------------------------------------
# Output format for logging, possible values: default, json
logFormat: default
@ -123,15 +109,6 @@ logFormat: default
offline: false
# ---------------------------------------------------------------------
# High-Availability
# ---------------------------------------------------------------------
# Set to true if you have multiple concurrent instances running off the
# same DB (e.g. Kubernetes pods / load balanced instances). Leave false
# otherwise. You MUST be using PostgreSQL to use this feature.
ha: false
# ---------------------------------------------------------------------
# Data Path
# ---------------------------------------------------------------------

@ -1,15 +1,13 @@
{
"name": "wiki",
"version": "2.0.0",
"releaseDate": "2019-01-01T01:01:01.000Z",
"description": "A modern, lightweight and powerful wiki app built on NodeJS, Git and Markdown",
"version": "3.0.0",
"releaseDate": "2022-01-01T01:01:01.000Z",
"description": "The most powerful and extensible open source Wiki software",
"main": "wiki.js",
"dev": true,
"scripts": {
"start": "node server",
"dev": "node dev",
"build": "webpack --profile --config dev/webpack/webpack.prod.js",
"watch": "webpack --config dev/webpack/webpack.dev.js",
"test": "eslint --format codeframe --ext .js,.vue . && pug-lint server/views && jest",
"cypress:open": "cypress open"
},
@ -33,7 +31,7 @@
},
"homepage": "https://github.com/Requarks/wiki#readme",
"engines": {
"node": ">=10.12"
"node": ">=16.0"
},
"dependencies": {
"@azure/storage-blob": "12.2.1",

@ -8,16 +8,17 @@ defaults:
# File defaults
port: 80
db:
type: postgres
host: localhost
port: 5432
user: wikijs
pass: wikijsrocks
db: wiki
ssl: false
storage: ./db.sqlite
sslOptions:
auto: true
schemas:
wiki: wiki
scheduler: scheduler
ssl:
enabled: false
pool:
@ -26,7 +27,6 @@ defaults:
logLevel: info
logFormat: default
offline: false
ha: false
bodyParserLimit: 5mb
# DB defaults
api:
@ -88,7 +88,7 @@ defaults:
ldapdebug: false
sqllog: false
# System defaults
channel: STABLE
channel: NEXT
setup: false
dataPath: ./data
cors:

@ -7,7 +7,7 @@ const fs = require('fs')
const Objection = require('objection')
const migrationSource = require('../db/migrator-source')
const migrateFromBeta = require('../db/beta')
const migrateFromLegacy = require('../db/legacy')
/* global WIKI */
@ -20,15 +20,12 @@ module.exports = {
listener: null,
/**
* Initialize DB
*
* @return {Object} DB instance
*/
init() {
let self = this
// Fetch DB Config
let dbClient = null
let dbConfig = (!_.isEmpty(process.env.DATABASE_URL)) ? process.env.DATABASE_URL : {
host: WIKI.config.db.host.toString(),
user: WIKI.config.db.user.toString(),
@ -74,84 +71,22 @@ module.exports = {
}
}
// Engine-specific config
switch (WIKI.config.db.type) {
case 'postgres':
dbClient = 'pg'
if (dbUseSSL && _.isPlainObject(dbConfig)) {
dbConfig.ssl = (sslOptions === true) ? { rejectUnauthorized: true } : sslOptions
}
break
case 'mariadb':
case 'mysql':
dbClient = 'mysql2'
if (dbUseSSL && _.isPlainObject(dbConfig)) {
dbConfig.ssl = sslOptions
}
// Fix mysql boolean handling...
dbConfig.typeCast = (field, next) => {
if (field.type === 'TINY' && field.length === 1) {
let value = field.string()
return value ? (value === '1') : null
}
return next()
}
break
case 'mssql':
dbClient = 'mssql'
if (_.isPlainObject(dbConfig)) {
dbConfig.appName = 'Wiki.js'
_.set(dbConfig, 'options.appName', 'Wiki.js')
dbConfig.enableArithAbort = true
_.set(dbConfig, 'options.enableArithAbort', true)
if (dbUseSSL) {
dbConfig.encrypt = true
_.set(dbConfig, 'options.encrypt', true)
}
}
break
case 'sqlite':
dbClient = 'sqlite3'
dbConfig = { filename: WIKI.config.db.storage }
break
default:
WIKI.logger.error('Invalid DB Type')
process.exit(1)
if (dbUseSSL && _.isPlainObject(dbConfig)) {
dbConfig.ssl = (sslOptions === true) ? { rejectUnauthorized: true } : sslOptions
}
// Initialize Knex
this.knex = Knex({
client: dbClient,
client: 'pg',
useNullAsDefault: true,
asyncStackTraces: WIKI.IS_DEBUG,
connection: dbConfig,
searchPath: [WIKI.config.db.schemas.wiki],
pool: {
...WIKI.config.pool,
async afterCreate(conn, done) {
// -> Set Connection App Name
switch (WIKI.config.db.type) {
case 'postgres':
await conn.query(`set application_name = 'Wiki.js'`)
// -> Set schema if it's not public
if (WIKI.config.db.schema && WIKI.config.db.schema !== 'public') {
await conn.query(`set search_path TO ${WIKI.config.db.schema}, public;`)
}
done()
break
case 'mysql':
await conn.promise().query(`set autocommit = 1`)
done()
break
default:
done()
break
}
await conn.query(`set application_name = 'Wiki.js'`)
}
},
debug: WIKI.IS_DEBUG
@ -191,18 +126,19 @@ module.exports = {
async syncSchemas () {
return self.knex.migrate.latest({
tableName: 'migrations',
migrationSource
migrationSource,
schemaName: WIKI.config.db.schemas.wiki
})
},
// -> Migrate DB Schemas from beta
async migrateFromBeta () {
return migrateFromBeta.migrate(self.knex)
// -> Migrate DB Schemas from 2.x
async migrateFromLegacy () {
return migrateFromLegacy.migrate(self.knex)
}
}
let initTasksQueue = (WIKI.IS_MASTER) ? [
initTasks.connect,
initTasks.migrateFromBeta,
initTasks.migrateFromLegacy,
initTasks.syncSchemas
] : [
() => { return Promise.resolve() }
@ -210,7 +146,6 @@ module.exports = {
// Perform init tasks
WIKI.logger.info(`Using database driver ${dbClient} for ${WIKI.config.db.type} [ OK ]`)
this.onReady = Promise.each(initTasksQueue, t => t()).return(true)
return {
@ -222,14 +157,6 @@ module.exports = {
* Subscribe to database LISTEN / NOTIFY for multi-instances events
*/
async subscribeToNotifications () {
const useHA = (WIKI.config.ha === true || WIKI.config.ha === 'true' || WIKI.config.ha === 1 || WIKI.config.ha === '1')
if (!useHA) {
return
} else if (WIKI.config.db.type !== 'postgres') {
WIKI.logger.warn(`Database engine doesn't support pub/sub. Will not handle concurrent instances: [ DISABLED ]`)
return
}
const PGPubSub = require('pg-pubsub')
this.listener = new PGPubSub(this.knex.client.connectionSettings, {
@ -254,7 +181,7 @@ module.exports = {
WIKI.configSvc.subscribeToEvents()
WIKI.models.pages.subscribeToEvents()
WIKI.logger.info(`High-Availability Listener initialized successfully: [ OK ]`)
WIKI.logger.info(`PG PubSub Listener initialized successfully: [ OK ]`)
},
/**
* Unsubscribe from database LISTEN / NOTIFY

@ -1,115 +0,0 @@
const _ = require('lodash')
const path = require('path')
const fs = require('fs-extra')
const semver = require('semver')
/* global WIKI */
module.exports = {
async migrate (knex) {
const migrationsTableExists = await knex.schema.hasTable('migrations')
if (!migrationsTableExists) {
return
}
const dbCompat = {
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
}
const migrations = await knex('migrations')
if (_.some(migrations, m => m.name.indexOf('2.0.0-beta') >= 0)) {
// -> Pre-beta.241 locale field length fix
const localeColnInfo = await knex('pages').columnInfo('localeCode')
if (WIKI.config.db.type !== 'sqlite' && localeColnInfo.maxLength === 2) {
// -> Load locales
const locales = await knex('locales')
await knex.schema
// -> Remove constraints
.table('users', table => {
table.dropForeign('localeCode')
})
.table('pages', table => {
table.dropForeign('localeCode')
})
.table('pageHistory', table => {
table.dropForeign('localeCode')
})
.table('pageTree', table => {
table.dropForeign('localeCode')
})
// -> Recreate locales table
.dropTable('locales')
.createTable('locales', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('code', 5).notNullable().primary()
table.json('strings')
table.boolean('isRTL').notNullable().defaultTo(false)
table.string('name').notNullable()
table.string('nativeName').notNullable()
table.integer('availability').notNullable().defaultTo(0)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
await knex('locales').insert(locales)
// -> Alter columns length
await knex.schema
.table('users', table => {
table.string('localeCode', 5).notNullable().defaultTo('en').alter()
})
.table('pages', table => {
table.string('localeCode', 5).alter()
})
.table('pageHistory', table => {
table.string('localeCode', 5).alter()
})
.table('pageTree', table => {
table.string('localeCode', 5).alter()
})
// -> Restore restraints
.table('users', table => {
table.foreign('localeCode').references('code').inTable('locales')
})
.table('pages', table => {
table.foreign('localeCode').references('code').inTable('locales')
})
.table('pageHistory', table => {
table.foreign('localeCode').references('code').inTable('locales')
})
.table('pageTree', table => {
table.foreign('localeCode').references('code').inTable('locales')
})
}
// -> Advance to latest beta/rc migration state
const baseMigrationPath = path.join(WIKI.SERVERPATH, (WIKI.config.db.type !== 'sqlite') ? 'db/beta/migrations' : 'db/beta/migrations-sqlite')
await knex.migrate.latest({
tableName: 'migrations',
migrationSource: {
async getMigrations() {
const migrationFiles = await fs.readdir(baseMigrationPath)
return migrationFiles.sort(semver.compare).map(m => ({
file: m,
directory: baseMigrationPath
}))
},
getMigrationName(migration) {
return migration.file
},
getMigration(migration) {
return require(path.join(baseMigrationPath, migration.file))
}
}
})
// -> Cleanup migration table
await knex('migrations').truncate()
// -> Advance to stable 2.0 migration state
await knex('migrations').insert({
name: '2.0.0.js',
batch: 1,
migration_time: knex.fn.now()
})
}
}
}

@ -1,259 +0,0 @@
exports.up = knex => {
return knex.schema
// =====================================
// MODEL TABLES
// =====================================
// ASSETS ------------------------------
.createTable('assets', table => {
table.increments('id').primary()
table.string('filename').notNullable()
table.string('basename').notNullable()
table.string('ext').notNullable()
table.enum('kind', ['binary', 'image']).notNullable().defaultTo('binary')
table.string('mime').notNullable().defaultTo('application/octet-stream')
table.integer('fileSize').unsigned().comment('In kilobytes')
table.json('metadata')
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
table.integer('folderId').unsigned().references('id').inTable('assetFolders')
table.integer('authorId').unsigned().references('id').inTable('users')
})
// ASSET FOLDERS -----------------------
.createTable('assetFolders', table => {
table.increments('id').primary()
table.string('name').notNullable()
table.string('slug').notNullable()
table.integer('parentId').unsigned().references('id').inTable('assetFolders')
})
// AUTHENTICATION ----------------------
.createTable('authentication', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
table.boolean('selfRegistration').notNullable().defaultTo(false)
table.json('domainWhitelist').notNullable()
table.json('autoEnrollGroups').notNullable()
})
// COMMENTS ----------------------------
.createTable('comments', table => {
table.increments('id').primary()
table.text('content').notNullable()
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
table.integer('pageId').unsigned().references('id').inTable('pages')
table.integer('authorId').unsigned().references('id').inTable('users')
})
// EDITORS -----------------------------
.createTable('editors', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
})
// GROUPS ------------------------------
.createTable('groups', table => {
table.increments('id').primary()
table.string('name').notNullable()
table.json('permissions').notNullable()
table.json('pageRules').notNullable()
table.boolean('isSystem').notNullable().defaultTo(false)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// LOCALES -----------------------------
.createTable('locales', table => {
table.string('code', 5).notNullable().primary()
table.json('strings')
table.boolean('isRTL').notNullable().defaultTo(false)
table.string('name').notNullable()
table.string('nativeName').notNullable()
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// LOGGING ----------------------------
.createTable('loggers', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.string('level').notNullable().defaultTo('warn')
table.json('config')
})
// NAVIGATION ----------------------------
.createTable('navigation', table => {
table.string('key').notNullable().primary()
table.json('config')
})
// PAGE HISTORY ------------------------
.createTable('pageHistory', table => {
table.increments('id').primary()
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable()
table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isPublished').notNullable().defaultTo(false)
table.string('publishStartDate')
table.string('publishEndDate')
table.text('content')
table.string('contentType').notNullable()
table.string('createdAt').notNullable()
table.integer('pageId').unsigned().references('id').inTable('pages')
table.string('editorKey').references('key').inTable('editors')
table.string('localeCode', 5).references('code').inTable('locales')
table.integer('authorId').unsigned().references('id').inTable('users')
})
// PAGES -------------------------------
.createTable('pages', table => {
table.increments('id').primary()
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable()
table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isPublished').notNullable().defaultTo(false)
table.string('privateNS')
table.string('publishStartDate')
table.string('publishEndDate')
table.text('content')
table.text('render')
table.json('toc')
table.string('contentType').notNullable()
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
table.string('editorKey').references('key').inTable('editors')
table.string('localeCode', 5).references('code').inTable('locales')
table.integer('authorId').unsigned().references('id').inTable('users')
table.integer('creatorId').unsigned().references('id').inTable('users')
})
// PAGE TREE ---------------------------
.createTable('pageTree', table => {
table.increments('id').primary()
table.string('path').notNullable()
table.integer('depth').unsigned().notNullable()
table.string('title').notNullable()
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isFolder').notNullable().defaultTo(false)
table.string('privateNS')
table.integer('parent').unsigned().references('id').inTable('pageTree')
table.integer('pageId').unsigned().references('id').inTable('pages')
table.string('localeCode', 5).references('code').inTable('locales')
})
// RENDERERS ---------------------------
.createTable('renderers', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config')
})
// SEARCH ------------------------------
.createTable('searchEngines', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config')
})
// SETTINGS ----------------------------
.createTable('settings', table => {
table.string('key').notNullable().primary()
table.json('value')
table.string('updatedAt').notNullable()
})
// STORAGE -----------------------------
.createTable('storage', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push')
table.json('config')
})
// TAGS --------------------------------
.createTable('tags', table => {
table.increments('id').primary()
table.string('tag').notNullable().unique()
table.string('title')
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// USER KEYS ---------------------------
.createTable('userKeys', table => {
table.increments('id').primary()
table.string('kind').notNullable()
table.string('token').notNullable()
table.string('createdAt').notNullable()
table.string('validUntil').notNullable()
table.integer('userId').unsigned().references('id').inTable('users')
})
// USERS -------------------------------
.createTable('users', table => {
table.increments('id').primary()
table.string('email').notNullable()
table.string('name').notNullable()
table.string('providerId')
table.string('password')
table.boolean('tfaIsActive').notNullable().defaultTo(false)
table.string('tfaSecret')
table.string('jobTitle').defaultTo('')
table.string('location').defaultTo('')
table.string('pictureUrl')
table.string('timezone').notNullable().defaultTo('America/New_York')
table.boolean('isSystem').notNullable().defaultTo(false)
table.boolean('isActive').notNullable().defaultTo(false)
table.boolean('isVerified').notNullable().defaultTo(false)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local')
table.string('localeCode', 5).references('code').inTable('locales').notNullable().defaultTo('en')
table.string('defaultEditor').references('key').inTable('editors').notNullable().defaultTo('markdown')
})
// =====================================
// RELATION TABLES
// =====================================
// PAGE HISTORY TAGS ---------------------------
.createTable('pageHistoryTags', table => {
table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE')
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
})
// PAGE TAGS ---------------------------
.createTable('pageTags', table => {
table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
})
// USER GROUPS -------------------------
.createTable('userGroups', table => {
table.increments('id').primary()
table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE')
table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE')
})
// =====================================
// REFERENCES
// =====================================
.table('users', table => {
table.unique(['providerKey', 'email'])
})
}
exports.down = knex => {
return knex.schema
.dropTableIfExists('userGroups')
.dropTableIfExists('pageHistoryTags')
.dropTableIfExists('pageHistory')
.dropTableIfExists('pageTags')
.dropTableIfExists('assets')
.dropTableIfExists('assetFolders')
.dropTableIfExists('comments')
.dropTableIfExists('editors')
.dropTableIfExists('groups')
.dropTableIfExists('locales')
.dropTableIfExists('navigation')
.dropTableIfExists('pages')
.dropTableIfExists('renderers')
.dropTableIfExists('settings')
.dropTableIfExists('storage')
.dropTableIfExists('tags')
.dropTableIfExists('userKeys')
.dropTableIfExists('users')
}

@ -1,52 +0,0 @@
exports.up = knex => {
return knex.schema
.renameTable('pageHistory', 'pageHistory_old')
.createTable('pageHistory', table => {
table.increments('id').primary()
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable()
table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isPublished').notNullable().defaultTo(false)
table.string('publishStartDate')
table.string('publishEndDate')
table.text('content')
table.string('contentType').notNullable()
table.string('createdAt').notNullable()
table.string('action').defaultTo('updated')
table.integer('pageId').unsigned()
table.string('editorKey').references('key').inTable('editors')
table.string('localeCode', 5).references('code').inTable('locales')
table.integer('authorId').unsigned().references('id').inTable('users')
})
.raw(`INSERT INTO pageHistory SELECT id,path,hash,title,description,isPrivate,isPublished,publishStartDate,publishEndDate,content,contentType,createdAt,'updated' AS action,pageId,editorKey,localeCode,authorId FROM pageHistory_old;`)
.dropTable('pageHistory_old')
}
exports.down = knex => {
return knex.schema
.renameTable('pageHistory', 'pageHistory_old')
.createTable('pageHistory', table => {
table.increments('id').primary()
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable()
table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isPublished').notNullable().defaultTo(false)
table.string('publishStartDate')
table.string('publishEndDate')
table.text('content')
table.string('contentType').notNullable()
table.string('createdAt').notNullable()
table.integer('pageId').unsigned().references('id').inTable('pages')
table.string('editorKey').references('key').inTable('editors')
table.string('localeCode', 5).references('code').inTable('locales')
table.integer('authorId').unsigned().references('id').inTable('users')
})
.raw('INSERT INTO pageHistory SELECT id,path,hash,title,description,isPrivate,isPublished,publishStartDate,publishEndDate,content,contentType,createdAt,NULL as pageId,editorKey,localeCode,authorId FROM pageHistory_old;')
.dropTable('pageHistory_old')
}

@ -1,15 +0,0 @@
exports.up = knex => {
return knex.schema
.table('assets', table => {
table.dropColumn('basename')
table.string('hash').notNullable().defaultTo('')
})
}
exports.down = knex => {
return knex.schema
.table('assets', table => {
table.dropColumn('hash')
table.string('basename').notNullable().defaultTo('')
})
}

@ -1,13 +0,0 @@
exports.up = knex => {
return knex.schema
.createTable('analytics', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
})
}
exports.down = knex => {
return knex.schema
.dropTableIfExists('analytics')
}

@ -1,13 +0,0 @@
exports.up = knex => {
return knex.schema
.table('locales', table => {
table.integer('availability').notNullable().defaultTo(0)
})
}
exports.down = knex => {
return knex.schema
.table('locales', table => {
table.dropColumn('availability')
})
}

@ -1,13 +0,0 @@
exports.up = knex => {
return knex.schema
.table('users', table => {
table.boolean('mustChangePwd').notNullable().defaultTo(false)
})
}
exports.down = knex => {
return knex.schema
.table('users', table => {
table.dropColumn('mustChangePwd')
})
}

@ -1,17 +0,0 @@
exports.up = knex => {
return knex.schema
.createTable('pageLinks', table => {
table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.string('path').notNullable()
table.string('localeCode', 5).notNullable()
})
.table('pageLinks', table => {
table.index(['path', 'localeCode'])
})
}
exports.down = knex => {
return knex.schema
.dropTableIfExists('pageLinks')
}

@ -1,15 +0,0 @@
exports.up = knex => {
return knex.schema
.table('storage', table => {
table.string('syncInterval')
table.json('state')
})
}
exports.down = knex => {
return knex.schema
.table('storage', table => {
table.dropColumn('syncInterval')
table.dropColumn('state')
})
}

@ -1,12 +0,0 @@
exports.up = knex => {
return knex.schema
.createTable('assetData', table => {
table.integer('id').primary()
table.binary('data').notNullable()
})
}
exports.down = knex => {
return knex.schema
.dropTableIfExists('assetData')
}

@ -1,35 +0,0 @@
exports.up = knex => {
return knex.schema
.dropTable('pageTree')
.createTable('pageTree', table => {
table.integer('id').primary()
table.string('path').notNullable()
table.integer('depth').unsigned().notNullable()
table.string('title').notNullable()
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isFolder').notNullable().defaultTo(false)
table.string('privateNS')
table.integer('parent').unsigned().references('id').inTable('pageTree').onDelete('CASCADE')
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.string('localeCode', 5).references('code').inTable('locales')
})
}
exports.down = knex => {
return knex.schema
.dropTable('pageTree')
.createTable('pageTree', table => {
table.integer('id').primary()
table.string('path').notNullable()
table.integer('depth').unsigned().notNullable()
table.string('title').notNullable()
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isFolder').notNullable().defaultTo(false)
table.string('privateNS')
table.integer('parent').unsigned().references('id').inTable('pageTree')
table.integer('pageId').unsigned().references('id').inTable('pages')
table.string('localeCode', 5).references('code').inTable('locales')
})
}

@ -1,292 +0,0 @@
/* global WIKI */
exports.up = knex => {
const dbCompat = {
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
}
return knex.schema
// =====================================
// MODEL TABLES
// =====================================
// ASSETS ------------------------------
.createTable('assets', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('filename').notNullable()
table.string('basename').notNullable()
table.string('ext').notNullable()
table.enum('kind', ['binary', 'image']).notNullable().defaultTo('binary')
table.string('mime').notNullable().defaultTo('application/octet-stream')
table.integer('fileSize').unsigned().comment('In kilobytes')
table.json('metadata')
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// ASSET FOLDERS -----------------------
.createTable('assetFolders', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('name').notNullable()
table.string('slug').notNullable()
table.integer('parentId').unsigned().references('id').inTable('assetFolders')
})
// AUTHENTICATION ----------------------
.createTable('authentication', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
table.boolean('selfRegistration').notNullable().defaultTo(false)
table.json('domainWhitelist').notNullable()
table.json('autoEnrollGroups').notNullable()
})
// COMMENTS ----------------------------
.createTable('comments', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.text('content').notNullable()
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// EDITORS -----------------------------
.createTable('editors', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
})
// GROUPS ------------------------------
.createTable('groups', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('name').notNullable()
table.json('permissions').notNullable()
table.json('pageRules').notNullable()
table.boolean('isSystem').notNullable().defaultTo(false)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// LOCALES -----------------------------
.createTable('locales', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('code', 2).notNullable().primary()
table.json('strings')
table.boolean('isRTL').notNullable().defaultTo(false)
table.string('name').notNullable()
table.string('nativeName').notNullable()
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// LOGGING ----------------------------
.createTable('loggers', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.string('level').notNullable().defaultTo('warn')
table.json('config')
})
// NAVIGATION ----------------------------
.createTable('navigation', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.json('config')
})
// PAGE HISTORY ------------------------
.createTable('pageHistory', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable()
table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isPublished').notNullable().defaultTo(false)
table.string('publishStartDate')
table.string('publishEndDate')
table.text('content')
table.string('contentType').notNullable()
table.string('createdAt').notNullable()
})
// PAGES -------------------------------
.createTable('pages', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable()
table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isPublished').notNullable().defaultTo(false)
table.string('privateNS')
table.string('publishStartDate')
table.string('publishEndDate')
table.text('content')
table.text('render')
table.json('toc')
table.string('contentType').notNullable()
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// PAGE TREE ---------------------------
.createTable('pageTree', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('path').notNullable()
table.integer('depth').unsigned().notNullable()
table.string('title').notNullable()
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isFolder').notNullable().defaultTo(false)
table.string('privateNS')
})
// RENDERERS ---------------------------
.createTable('renderers', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config')
})
// SEARCH ------------------------------
.createTable('searchEngines', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config')
})
// SETTINGS ----------------------------
.createTable('settings', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.json('value')
table.string('updatedAt').notNullable()
})
// STORAGE -----------------------------
.createTable('storage', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push')
table.json('config')
})
// TAGS --------------------------------
.createTable('tags', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('tag').notNullable().unique()
table.string('title')
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// USER KEYS ---------------------------
.createTable('userKeys', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('kind').notNullable()
table.string('token').notNullable()
table.string('createdAt').notNullable()
table.string('validUntil').notNullable()
})
// USERS -------------------------------
.createTable('users', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('email').notNullable()
table.string('name').notNullable()
table.string('providerId')
table.string('password')
table.boolean('tfaIsActive').notNullable().defaultTo(false)
table.string('tfaSecret')
table.string('jobTitle').defaultTo('')
table.string('location').defaultTo('')
table.string('pictureUrl')
table.string('timezone').notNullable().defaultTo('America/New_York')
table.boolean('isSystem').notNullable().defaultTo(false)
table.boolean('isActive').notNullable().defaultTo(false)
table.boolean('isVerified').notNullable().defaultTo(false)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// =====================================
// RELATION TABLES
// =====================================
// PAGE HISTORY TAGS ---------------------------
.createTable('pageHistoryTags', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE')
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
})
// PAGE TAGS ---------------------------
.createTable('pageTags', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
})
// USER GROUPS -------------------------
.createTable('userGroups', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE')
table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE')
})
// =====================================
// REFERENCES
// =====================================
.table('assets', table => {
table.integer('folderId').unsigned().references('id').inTable('assetFolders')
table.integer('authorId').unsigned().references('id').inTable('users')
})
.table('comments', table => {
table.integer('pageId').unsigned().references('id').inTable('pages')
table.integer('authorId').unsigned().references('id').inTable('users')
})
.table('pageHistory', table => {
table.integer('pageId').unsigned().references('id').inTable('pages')
table.string('editorKey').references('key').inTable('editors')
table.string('localeCode', 2).references('code').inTable('locales')
table.integer('authorId').unsigned().references('id').inTable('users')
})
.table('pages', table => {
table.string('editorKey').references('key').inTable('editors')
table.string('localeCode', 2).references('code').inTable('locales')
table.integer('authorId').unsigned().references('id').inTable('users')
table.integer('creatorId').unsigned().references('id').inTable('users')
})
.table('pageTree', table => {
table.integer('parent').unsigned().references('id').inTable('pageTree')
table.integer('pageId').unsigned().references('id').inTable('pages')
table.string('localeCode', 2).references('code').inTable('locales')
})
.table('userKeys', table => {
table.integer('userId').unsigned().references('id').inTable('users')
})
.table('users', table => {
table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local')
table.string('localeCode', 2).references('code').inTable('locales').notNullable().defaultTo('en')
table.string('defaultEditor').references('key').inTable('editors').notNullable().defaultTo('markdown')
table.unique(['providerKey', 'email'])
})
}
exports.down = knex => {
return knex.schema
.dropTableIfExists('userGroups')
.dropTableIfExists('pageHistoryTags')
.dropTableIfExists('pageHistory')
.dropTableIfExists('pageTags')
.dropTableIfExists('assets')
.dropTableIfExists('assetFolders')
.dropTableIfExists('comments')
.dropTableIfExists('editors')
.dropTableIfExists('groups')
.dropTableIfExists('locales')
.dropTableIfExists('navigation')
.dropTableIfExists('pages')
.dropTableIfExists('renderers')
.dropTableIfExists('settings')
.dropTableIfExists('storage')
.dropTableIfExists('tags')
.dropTableIfExists('userKeys')
.dropTableIfExists('users')
}

@ -1,15 +0,0 @@
exports.up = knex => {
return knex.schema
.table('pageHistory', table => {
table.string('action').defaultTo('updated')
table.dropForeign('pageId')
})
}
exports.down = knex => {
return knex.schema
.table('pageHistory', table => {
table.dropColumn('action')
table.integer('pageId').unsigned().references('id').inTable('pages')
})
}

@ -1,15 +0,0 @@
exports.up = knex => {
return knex.schema
.table('assets', table => {
table.dropColumn('basename')
table.string('hash').notNullable()
})
}
exports.down = knex => {
return knex.schema
.table('assets', table => {
table.dropColumn('hash')
table.string('basename').notNullable()
})
}

@ -1,23 +0,0 @@
/* global WIKI */
exports.up = knex => {
const dbCompat = {
blobLength: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
}
return knex.schema
.table('assetData', table => {
if (dbCompat.blobLength) {
table.dropColumn('data')
}
})
.table('assetData', table => {
if (dbCompat.blobLength) {
table.specificType('data', 'LONGBLOB').notNullable()
}
})
}
exports.down = knex => {
return knex.schema
.table('assetData', table => {})
}

@ -1,19 +0,0 @@
/* global WIKI */
exports.up = knex => {
const dbCompat = {
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
}
return knex.schema
.createTable('analytics', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
})
}
exports.down = knex => {
return knex.schema
.dropTableIfExists('analytics')
}

@ -1,13 +0,0 @@
exports.up = knex => {
return knex.schema
.table('locales', table => {
table.integer('availability').notNullable().defaultTo(0)
})
}
exports.down = knex => {
return knex.schema
.table('locales', table => {
table.dropColumn('availability')
})
}

@ -1,13 +0,0 @@
exports.up = knex => {
return knex.schema
.table('users', table => {
table.boolean('mustChangePwd').notNullable().defaultTo(false)
})
}
exports.down = knex => {
return knex.schema
.table('users', table => {
table.dropColumn('mustChangePwd')
})
}

@ -1,23 +0,0 @@
/* global WIKI */
exports.up = knex => {
const dbCompat = {
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
}
return knex.schema
.createTable('pageLinks', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.string('path').notNullable()
table.string('localeCode', 5).notNullable()
})
.table('pageLinks', table => {
table.index(['path', 'localeCode'])
})
}
exports.down = knex => {
return knex.schema
.dropTableIfExists('pageLinks')
}

@ -1,15 +0,0 @@
exports.up = knex => {
return knex.schema
.table('storage', table => {
table.string('syncInterval')
table.json('state')
})
}
exports.down = knex => {
return knex.schema
.table('storage', table => {
table.dropColumn('syncInterval')
table.dropColumn('state')
})
}

@ -1,18 +0,0 @@
/* global WIKI */
exports.up = knex => {
const dbCompat = {
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
}
return knex.schema
.createTable('assetData', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.integer('id').primary()
table.binary('data').notNullable()
})
}
exports.down = knex => {
return knex.schema
.dropTableIfExists('assetData')
}

@ -1,54 +0,0 @@
/* global WIKI */
exports.up = async knex => {
const dbCompat = {
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`),
selfCascadeDelete: WIKI.config.db.type !== 'mssql'
}
return knex.schema
.dropTable('pageTree')
.createTable('pageTree', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.integer('id').unsigned().primary()
table.string('path').notNullable()
table.integer('depth').unsigned().notNullable()
table.string('title').notNullable()
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isFolder').notNullable().defaultTo(false)
table.string('privateNS')
})
.table('pageTree', table => {
if (dbCompat.selfCascadeDelete) {
table.integer('parent').unsigned().references('id').inTable('pageTree').onDelete('CASCADE')
} else {
table.integer('parent').unsigned()
}
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.string('localeCode', 5).references('code').inTable('locales')
})
}
exports.down = knex => {
const dbCompat = {
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`),
selfCascadeDelete: WIKI.config.db.type !== 'mssql'
}
return knex.schema
.dropTable('pageTree')
.createTable('pageTree', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.integer('id').primary()
table.string('path').notNullable()
table.integer('depth').unsigned().notNullable()
table.string('title').notNullable()
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isFolder').notNullable().defaultTo(false)
table.string('privateNS')
})
.table('pageTree', table => {
table.integer('parent').unsigned().references('id').inTable('pageTree')
table.integer('pageId').unsigned().references('id').inTable('pages')
table.string('localeCode', 5).references('code').inTable('locales')
})
}

@ -1,20 +0,0 @@
/* global WIKI */
exports.up = knex => {
return knex.schema
.table('pages', table => {
switch (WIKI.config.db.type) {
case 'mariadb':
case 'mysql':
table.specificType('content', 'LONGTEXT').alter()
table.specificType('render', 'LONGTEXT').alter()
break
case 'mssql':
table.specificType('content', 'VARCHAR(max)').alter()
table.specificType('render', 'VARCHAR(max)').alter()
break
}
})
}
exports.down = knex => { }

@ -0,0 +1,32 @@
const _ = require('lodash')
/* global WIKI */
module.exports = {
async migrate (knex) {
const migrationsTableExists = await knex.schema.hasTable('migrations')
if (!migrationsTableExists) {
return
}
const migrations = await knex('migrations')
if (_.some(migrations, m => m.name.indexOf('2.5.128') >= 0)) {
// TODO: 2.x MIGRATIONS for 3.0
WIKI.logger.error('Upgrading from 2.x is not yet supported. A future release will allow for upgrade from 2.x. Exiting...')
process.exit(1)
// -> Cleanup migration table
await knex('migrations').truncate()
// -> Advance to stable 3.0 migration state
await knex('migrations').insert({
name: '3.0.0.js',
batch: 1,
migration_time: knex.fn.now()
})
} else {
console.error('CANNOT UPGRADE FROM OLDER UNSUPPORTED VERSION. UPGRADE TO THE LATEST 2.X VERSION FIRST! Exiting...')
process.exit(1)
}
}
}

@ -1,268 +0,0 @@
exports.up = knex => {
return knex.schema
// =====================================
// MODEL TABLES
// =====================================
// ANALYTICS ---------------------------
.createTable('analytics', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
})
// ASSETS ------------------------------
.createTable('assets', table => {
table.increments('id').primary()
table.string('filename').notNullable()
table.string('hash').notNullable().defaultTo('')
table.string('ext').notNullable()
table.enum('kind', ['binary', 'image']).notNullable().defaultTo('binary')
table.string('mime').notNullable().defaultTo('application/octet-stream')
table.integer('fileSize').unsigned().comment('In kilobytes')
table.json('metadata')
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
table.integer('folderId').unsigned().references('id').inTable('assetFolders')
table.integer('authorId').unsigned().references('id').inTable('users')
})
// ASSET DATA --------------------------
.createTable('assetData', table => {
table.integer('id').primary()
table.binary('data').notNullable()
})
// ASSET FOLDERS -----------------------
.createTable('assetFolders', table => {
table.increments('id').primary()
table.string('name').notNullable()
table.string('slug').notNullable()
table.integer('parentId').unsigned().references('id').inTable('assetFolders')
})
// AUTHENTICATION ----------------------
.createTable('authentication', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
table.boolean('selfRegistration').notNullable().defaultTo(false)
table.json('domainWhitelist').notNullable()
table.json('autoEnrollGroups').notNullable()
})
// COMMENTS ----------------------------
.createTable('comments', table => {
table.increments('id').primary()
table.text('content').notNullable()
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
table.integer('pageId').unsigned().references('id').inTable('pages')
table.integer('authorId').unsigned().references('id').inTable('users')
})
// EDITORS -----------------------------
.createTable('editors', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
})
// GROUPS ------------------------------
.createTable('groups', table => {
table.increments('id').primary()
table.string('name').notNullable()
table.json('permissions').notNullable()
table.json('pageRules').notNullable()
table.boolean('isSystem').notNullable().defaultTo(false)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// LOCALES -----------------------------
.createTable('locales', table => {
table.string('code', 5).notNullable().primary()
table.json('strings')
table.boolean('isRTL').notNullable().defaultTo(false)
table.string('name').notNullable()
table.string('nativeName').notNullable()
table.integer('availability').notNullable().defaultTo(0)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// LOGGING ----------------------------
.createTable('loggers', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.string('level').notNullable().defaultTo('warn')
table.json('config')
})
// NAVIGATION ----------------------------
.createTable('navigation', table => {
table.string('key').notNullable().primary()
table.json('config')
})
// PAGE HISTORY ------------------------
.createTable('pageHistory', table => {
table.increments('id').primary()
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable()
table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isPublished').notNullable().defaultTo(false)
table.string('publishStartDate')
table.string('publishEndDate')
table.text('content')
table.string('contentType').notNullable()
table.string('createdAt').notNullable()
table.string('action').defaultTo('updated')
table.integer('pageId').unsigned()
table.string('editorKey').references('key').inTable('editors')
table.string('localeCode', 5).references('code').inTable('locales')
table.integer('authorId').unsigned().references('id').inTable('users')
})
// PAGE LINKS --------------------------
.createTable('pageLinks', table => {
table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.string('path').notNullable()
table.string('localeCode', 5).notNullable()
})
// PAGES -------------------------------
.createTable('pages', table => {
table.increments('id').primary()
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable()
table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isPublished').notNullable().defaultTo(false)
table.string('privateNS')
table.string('publishStartDate')
table.string('publishEndDate')
table.text('content')
table.text('render')
table.json('toc')
table.string('contentType').notNullable()
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
table.string('editorKey').references('key').inTable('editors')
table.string('localeCode', 5).references('code').inTable('locales')
table.integer('authorId').unsigned().references('id').inTable('users')
table.integer('creatorId').unsigned().references('id').inTable('users')
})
// PAGE TREE ---------------------------
.createTable('pageTree', table => {
table.integer('id').primary()
table.string('path').notNullable()
table.integer('depth').unsigned().notNullable()
table.string('title').notNullable()
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isFolder').notNullable().defaultTo(false)
table.string('privateNS')
table.integer('parent').unsigned().references('id').inTable('pageTree').onDelete('CASCADE')
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.string('localeCode', 5).references('code').inTable('locales')
})
// RENDERERS ---------------------------
.createTable('renderers', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config')
})
// SEARCH ------------------------------
.createTable('searchEngines', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config')
})
// SETTINGS ----------------------------
.createTable('settings', table => {
table.string('key').notNullable().primary()
table.json('value')
table.string('updatedAt').notNullable()
})
// STORAGE -----------------------------
.createTable('storage', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push')
table.json('config')
table.string('syncInterval')
table.json('state')
})
// TAGS --------------------------------
.createTable('tags', table => {
table.increments('id').primary()
table.string('tag').notNullable().unique()
table.string('title')
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// USER KEYS ---------------------------
.createTable('userKeys', table => {
table.increments('id').primary()
table.string('kind').notNullable()
table.string('token').notNullable()
table.string('createdAt').notNullable()
table.string('validUntil').notNullable()
table.integer('userId').unsigned().references('id').inTable('users')
})
// USERS -------------------------------
.createTable('users', table => {
table.increments('id').primary()
table.string('email').notNullable()
table.string('name').notNullable()
table.string('providerId')
table.string('password')
table.boolean('tfaIsActive').notNullable().defaultTo(false)
table.string('tfaSecret')
table.string('jobTitle').defaultTo('')
table.string('location').defaultTo('')
table.string('pictureUrl')
table.string('timezone').notNullable().defaultTo('America/New_York')
table.boolean('isSystem').notNullable().defaultTo(false)
table.boolean('isActive').notNullable().defaultTo(false)
table.boolean('isVerified').notNullable().defaultTo(false)
table.boolean('mustChangePwd').notNullable().defaultTo(false)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local')
table.string('localeCode', 5).references('code').inTable('locales').notNullable().defaultTo('en')
table.string('defaultEditor').references('key').inTable('editors').notNullable().defaultTo('markdown')
})
// =====================================
// RELATION TABLES
// =====================================
// PAGE HISTORY TAGS ---------------------------
.createTable('pageHistoryTags', table => {
table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE')
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
})
// PAGE TAGS ---------------------------
.createTable('pageTags', table => {
table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
})
// USER GROUPS -------------------------
.createTable('userGroups', table => {
table.increments('id').primary()
table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE')
table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE')
})
// =====================================
// REFERENCES
// =====================================
.table('users', table => {
table.unique(['providerKey', 'email'])
})
// =====================================
// INDEXES
// =====================================
.table('pageLinks', table => {
table.index(['path', 'localeCode'])
})
}
exports.down = knex => { }

@ -1,9 +0,0 @@
exports.up = knex => {
return knex.schema
.alterTable('pageHistory', table => {
table.string('versionDate').notNullable().defaultTo('')
})
.raw(`UPDATE pageHistory AS h1 SET versionDate = COALESCE((SELECT createdAt FROM pageHistory AS h2 WHERE h2.pageId = h1.pageId AND h2.id < h1.id ORDER BY h2.id DESC LIMIT 1), h1.createdAt, '')`)
}
exports.down = knex => { }

@ -1,14 +0,0 @@
exports.up = knex => {
return knex.schema
.createTable('apiKeys', table => {
table.increments('id').primary()
table.string('name').notNullable()
table.text('key').notNullable()
table.string('expiration').notNullable()
table.boolean('isRevoked').notNullable().defaultTo(false)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
}
exports.down = knex => { }

@ -1,8 +0,0 @@
exports.up = knex => {
return knex.schema
.alterTable('users', table => {
table.string('lastLoginAt')
})
}
exports.down = knex => { }

@ -1,10 +0,0 @@
exports.up = knex => {
return knex.schema
.createTable('commentProviders', table => {
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
})
}
exports.down = knex => { }

@ -1,8 +0,0 @@
exports.up = knex => {
return knex.schema
.alterTable('pageTree', table => {
table.json('ancestors')
})
}
exports.down = knex => { }

@ -1,15 +0,0 @@
exports.up = knex => {
return knex.schema
.alterTable('pages', table => {
table.json('extra').notNullable().defaultTo('{}')
})
.alterTable('pageHistory', table => {
table.json('extra').notNullable().defaultTo('{}')
})
.alterTable('users', table => {
table.string('dateFormat').notNullable().defaultTo('')
table.string('appearance').notNullable().defaultTo('')
})
}
exports.down = knex => { }

@ -1,11 +0,0 @@
exports.up = knex => {
return knex.schema
.alterTable('comments', table => {
table.text('render').notNullable().defaultTo('')
table.string('name').notNullable().defaultTo('')
table.string('email').notNullable().defaultTo('')
table.string('ip').notNullable().defaultTo('')
})
}
exports.down = knex => { }

@ -1,8 +0,0 @@
exports.up = knex => {
return knex.schema
.alterTable('comments', table => {
table.integer('replyTo').unsigned().notNullable().defaultTo(0)
})
}
exports.down = knex => { }

@ -1,34 +0,0 @@
exports.up = async knex => {
// Check for users using disabled strategies
let protectedStrategies = []
const disabledStrategies = await knex('authentication').where('isEnabled', false)
if (disabledStrategies) {
const incompatibleUsers = await knex('users').distinct('providerKey').whereIn('providerKey', disabledStrategies.map(s => s.key))
if (incompatibleUsers && incompatibleUsers.length > 0) {
protectedStrategies = incompatibleUsers.map(u => u.providerKey)
}
}
// Delete disabled strategies
await knex('authentication').whereNotIn('key', protectedStrategies).andWhere('isEnabled', false).del()
// Update table schema
await knex.schema
.alterTable('authentication', table => {
table.integer('order').unsigned().notNullable().defaultTo(0)
table.string('strategyKey').notNullable().defaultTo('')
table.string('displayName').notNullable().defaultTo('')
})
// Fix pre-2.5 strategies
const strategies = await knex('authentication')
let idx = 1
for (const strategy of strategies) {
await knex('authentication').where('key', strategy.key).update({
strategyKey: strategy.key,
order: (strategy.key === 'local') ? 0 : idx++
})
}
}
exports.down = knex => { }

@ -1,14 +0,0 @@
const has = require('lodash/has')
exports.up = async knex => {
// -> Fix 2.5.1 added isEnabled columns for beta users
const localStrategy = await knex('authentication').where('key', 'local').first()
if (localStrategy && !has(localStrategy, 'isEnabled')) {
await knex.schema
.alterTable('authentication', table => {
table.boolean('isEnabled').notNullable().defaultTo(true)
})
}
}
exports.down = knex => { }

@ -1,6 +0,0 @@
exports.up = async knex => {
// -> Fix 2.5.117 new installations without isEnabled on local auth (#2382)
await knex('authentication').where('key', 'local').update({ isEnabled: true })
}
exports.down = knex => { }

@ -1,8 +0,0 @@
exports.up = async knex => {
await knex.schema
.alterTable('groups', table => {
table.string('redirectOnLogin').notNullable().defaultTo('/')
})
}
exports.down = knex => { }

@ -1,9 +0,0 @@
exports.up = knex => {
return knex.schema
.createTable('userAvatars', table => {
table.integer('id').primary()
table.binary('data').notNullable()
})
}
exports.down = knex => { }

@ -1,7 +0,0 @@
exports.up = async knex => {
await knex('users').update({
email: knex.raw('LOWER(email)')
})
}
exports.down = knex => { }

@ -1,325 +0,0 @@
/* global WIKI */
exports.up = knex => {
const dbCompat = {
blobLength: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`),
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`),
selfCascadeDelete: WIKI.config.db.type !== 'mssql'
}
return knex.schema
// =====================================
// MODEL TABLES
// =====================================
// ANALYTICS ---------------------------
.createTable('analytics', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
})
// ASSETS ------------------------------
.createTable('assets', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('filename').notNullable()
table.string('hash').notNullable()
table.string('ext').notNullable()
table.enum('kind', ['binary', 'image']).notNullable().defaultTo('binary')
table.string('mime').notNullable().defaultTo('application/octet-stream')
table.integer('fileSize').unsigned().comment('In kilobytes')
table.json('metadata')
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// ASSET DATA --------------------------
.createTable('assetData', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.integer('id').primary()
if (dbCompat.blobLength) {
table.specificType('data', 'LONGBLOB').notNullable()
} else {
table.binary('data').notNullable()
}
})
// ASSET FOLDERS -----------------------
.createTable('assetFolders', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('name').notNullable()
table.string('slug').notNullable()
table.integer('parentId').unsigned().references('id').inTable('assetFolders')
})
// AUTHENTICATION ----------------------
.createTable('authentication', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
table.boolean('selfRegistration').notNullable().defaultTo(false)
table.json('domainWhitelist').notNullable()
table.json('autoEnrollGroups').notNullable()
})
// COMMENTS ----------------------------
.createTable('comments', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.text('content').notNullable()
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// EDITORS -----------------------------
.createTable('editors', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
})
// GROUPS ------------------------------
.createTable('groups', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('name').notNullable()
table.json('permissions').notNullable()
table.json('pageRules').notNullable()
table.boolean('isSystem').notNullable().defaultTo(false)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// LOCALES -----------------------------
.createTable('locales', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('code', 5).notNullable().primary()
table.json('strings')
table.boolean('isRTL').notNullable().defaultTo(false)
table.string('name').notNullable()
table.string('nativeName').notNullable()
table.integer('availability').notNullable().defaultTo(0)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// LOGGING ----------------------------
.createTable('loggers', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.string('level').notNullable().defaultTo('warn')
table.json('config')
})
// NAVIGATION ----------------------------
.createTable('navigation', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.json('config')
})
// PAGE HISTORY ------------------------
.createTable('pageHistory', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable()
table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isPublished').notNullable().defaultTo(false)
table.string('publishStartDate')
table.string('publishEndDate')
table.string('action').defaultTo('updated')
table.integer('pageId').unsigned()
table.text('content')
table.string('contentType').notNullable()
table.string('createdAt').notNullable()
})
// PAGE LINKS --------------------------
.createTable('pageLinks', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('path').notNullable()
table.string('localeCode', 5).notNullable()
})
// PAGES -------------------------------
.createTable('pages', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable()
table.string('description')
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isPublished').notNullable().defaultTo(false)
table.string('privateNS')
table.string('publishStartDate')
table.string('publishEndDate')
switch (WIKI.config.db.type) {
case 'postgres':
case 'sqlite':
table.text('content')
table.text('render')
break
case 'mariadb':
case 'mysql':
table.specificType('content', 'LONGTEXT')
table.specificType('render', 'LONGTEXT')
break
case 'mssql':
table.specificType('content', 'VARCHAR(max)')
table.specificType('render', 'VARCHAR(max)')
break
}
table.json('toc')
table.string('contentType').notNullable()
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// PAGE TREE ---------------------------
.createTable('pageTree', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.integer('id').unsigned().primary()
table.string('path').notNullable()
table.integer('depth').unsigned().notNullable()
table.string('title').notNullable()
table.boolean('isPrivate').notNullable().defaultTo(false)
table.boolean('isFolder').notNullable().defaultTo(false)
table.string('privateNS')
})
// RENDERERS ---------------------------
.createTable('renderers', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config')
})
// SEARCH ------------------------------
.createTable('searchEngines', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config')
})
// SETTINGS ----------------------------
.createTable('settings', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.json('value')
table.string('updatedAt').notNullable()
})
// STORAGE -----------------------------
.createTable('storage', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.string('mode', ['sync', 'push', 'pull']).notNullable().defaultTo('push')
table.json('config')
table.string('syncInterval')
table.json('state')
})
// TAGS --------------------------------
.createTable('tags', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('tag').notNullable().unique()
table.string('title')
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// USER KEYS ---------------------------
.createTable('userKeys', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('kind').notNullable()
table.string('token').notNullable()
table.string('createdAt').notNullable()
table.string('validUntil').notNullable()
})
// USERS -------------------------------
.createTable('users', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('email').notNullable()
table.string('name').notNullable()
table.string('providerId')
table.string('password')
table.boolean('tfaIsActive').notNullable().defaultTo(false)
table.string('tfaSecret')
table.string('jobTitle').defaultTo('')
table.string('location').defaultTo('')
table.string('pictureUrl')
table.string('timezone').notNullable().defaultTo('America/New_York')
table.boolean('isSystem').notNullable().defaultTo(false)
table.boolean('isActive').notNullable().defaultTo(false)
table.boolean('isVerified').notNullable().defaultTo(false)
table.boolean('mustChangePwd').notNullable().defaultTo(false)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
// =====================================
// RELATION TABLES
// =====================================
// PAGE HISTORY TAGS ---------------------------
.createTable('pageHistoryTags', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pageHistory').onDelete('CASCADE')
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
})
// PAGE TAGS ---------------------------
.createTable('pageTags', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.integer('tagId').unsigned().references('id').inTable('tags').onDelete('CASCADE')
})
// USER GROUPS -------------------------
.createTable('userGroups', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.integer('userId').unsigned().references('id').inTable('users').onDelete('CASCADE')
table.integer('groupId').unsigned().references('id').inTable('groups').onDelete('CASCADE')
})
// =====================================
// REFERENCES
// =====================================
.table('assets', table => {
table.integer('folderId').unsigned().references('id').inTable('assetFolders')
table.integer('authorId').unsigned().references('id').inTable('users')
})
.table('comments', table => {
table.integer('pageId').unsigned().references('id').inTable('pages')
table.integer('authorId').unsigned().references('id').inTable('users')
})
.table('pageHistory', table => {
table.string('editorKey').references('key').inTable('editors')
table.string('localeCode', 5).references('code').inTable('locales')
table.integer('authorId').unsigned().references('id').inTable('users')
})
.table('pageLinks', table => {
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.index(['path', 'localeCode'])
})
.table('pages', table => {
table.string('editorKey').references('key').inTable('editors')
table.string('localeCode', 5).references('code').inTable('locales')
table.integer('authorId').unsigned().references('id').inTable('users')
table.integer('creatorId').unsigned().references('id').inTable('users')
})
.table('pageTree', table => {
if (dbCompat.selfCascadeDelete) {
table.integer('parent').unsigned().references('id').inTable('pageTree').onDelete('CASCADE')
} else {
table.integer('parent').unsigned()
}
table.integer('pageId').unsigned().references('id').inTable('pages').onDelete('CASCADE')
table.string('localeCode', 5).references('code').inTable('locales')
})
.table('userKeys', table => {
table.integer('userId').unsigned().references('id').inTable('users')
})
.table('users', table => {
table.string('providerKey').references('key').inTable('authentication').notNullable().defaultTo('local')
table.string('localeCode', 5).references('code').inTable('locales').notNullable().defaultTo('en')
table.string('defaultEditor').references('key').inTable('editors').notNullable().defaultTo('markdown')
table.unique(['providerKey', 'email'])
})
}
exports.down = knex => { }

@ -1,19 +0,0 @@
/* global WIKI */
exports.up = knex => {
return knex.schema
.alterTable('pageHistory', table => {
switch (WIKI.config.db.type) {
// No change needed for PostgreSQL and SQLite
case 'mariadb':
case 'mysql':
table.specificType('content', 'LONGTEXT').alter()
break
case 'mssql':
table.specificType('content', 'VARCHAR(max)').alter()
break
}
})
}
exports.down = knex => { }

@ -1,37 +0,0 @@
const _ = require('lodash')
/* global WIKI */
exports.up = async knex => {
let sqlVersionDate = ''
switch (WIKI.config.db.type) {
case 'postgres':
sqlVersionDate = 'UPDATE "pageHistory" h1 SET "versionDate" = COALESCE((SELECT prev."createdAt" FROM "pageHistory" prev WHERE prev."pageId" = h1."pageId" AND prev.id < h1.id ORDER BY prev.id DESC LIMIT 1), h1."createdAt")'
break
case 'mssql':
sqlVersionDate = 'UPDATE h1 SET "versionDate" = COALESCE((SELECT TOP 1 prev."createdAt" FROM "pageHistory" prev WHERE prev."pageId" = h1."pageId" AND prev.id < h1.id ORDER BY prev.id DESC), h1."createdAt") FROM "pageHistory" h1'
break
case 'mysql':
case 'mariadb':
// -> Fix for 2.2.50 failed migration
const pageHistoryColumns = await knex.schema.raw('SHOW COLUMNS FROM pageHistory')
if (_.some(pageHistoryColumns[0], ['Field', 'versionDate'])) {
console.info('MySQL 2.2.50 Migration Fix - Dropping failed versionDate column...')
await knex.schema.raw('ALTER TABLE pageHistory DROP COLUMN versionDate')
console.info('versionDate column dropped successfully.')
}
sqlVersionDate = `UPDATE pageHistory AS h1 INNER JOIN pageHistory AS h2 ON h2.id = (SELECT prev.id FROM (SELECT * FROM pageHistory) AS prev WHERE prev.pageId = h1.pageId AND prev.id < h1.id ORDER BY prev.id DESC LIMIT 1) SET h1.versionDate = h2.createdAt`
break
// case 'mariadb':
// sqlVersionDate = `UPDATE pageHistory AS h1 INNER JOIN pageHistory AS h2 ON h2.id = (SELECT prev.id FROM pageHistory AS prev WHERE prev.pageId = h1.pageId AND prev.id < h1.id ORDER BY prev.id DESC LIMIT 1) SET h1.versionDate = h2.createdAt`
// break
}
await knex.schema
.alterTable('pageHistory', table => {
table.string('versionDate').notNullable().defaultTo('')
})
.raw(sqlVersionDate)
}
exports.down = knex => { }

@ -1,20 +0,0 @@
/* global WIKI */
exports.up = knex => {
const dbCompat = {
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
}
return knex.schema
.createTable('apiKeys', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.increments('id').primary()
table.string('name').notNullable()
table.text('key').notNullable()
table.string('expiration').notNullable()
table.boolean('isRevoked').notNullable().defaultTo(false)
table.string('createdAt').notNullable()
table.string('updatedAt').notNullable()
})
}
exports.down = knex => { }

@ -1,8 +0,0 @@
exports.up = knex => {
return knex.schema
.alterTable('users', table => {
table.string('lastLoginAt')
})
}
exports.down = knex => { }

@ -1,8 +0,0 @@
exports.up = knex => {
return knex.schema
.alterTable('pageTree', table => {
table.json('ancestors')
})
}
exports.down = knex => { }

@ -1,25 +0,0 @@
/* global WIKI */
exports.up = knex => {
return knex.schema
.alterTable('pages', table => {
if (WIKI.config.db.type === 'mysql') {
table.json('extra')
} else {
table.json('extra').notNullable().defaultTo('{}')
}
})
.alterTable('pageHistory', table => {
if (WIKI.config.db.type === 'mysql') {
table.json('extra')
} else {
table.json('extra').notNullable().defaultTo('{}')
}
})
.alterTable('users', table => {
table.string('dateFormat').notNullable().defaultTo('')
table.string('appearance').notNullable().defaultTo('')
})
}
exports.down = knex => { }

@ -1,16 +0,0 @@
/* global WIKI */
exports.up = knex => {
const dbCompat = {
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
}
return knex.schema
.createTable('commentProviders', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.string('key').notNullable().primary()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.json('config').notNullable()
})
}
exports.down = knex => { }

@ -1,11 +0,0 @@
exports.up = knex => {
return knex.schema
.alterTable('comments', table => {
table.text('render').notNullable().defaultTo('')
table.string('name').notNullable().defaultTo('')
table.string('email').notNullable().defaultTo('')
table.string('ip').notNullable().defaultTo('')
})
}
exports.down = knex => { }

@ -1,8 +0,0 @@
exports.up = knex => {
return knex.schema
.alterTable('comments', table => {
table.integer('replyTo').unsigned().notNullable().defaultTo(0)
})
}
exports.down = knex => { }

@ -1,34 +0,0 @@
exports.up = async knex => {
// Check for users using disabled strategies
let protectedStrategies = []
const disabledStrategies = await knex('authentication').where('isEnabled', false)
if (disabledStrategies) {
const incompatibleUsers = await knex('users').distinct('providerKey').whereIn('providerKey', disabledStrategies.map(s => s.key))
if (incompatibleUsers && incompatibleUsers.length > 0) {
protectedStrategies = incompatibleUsers.map(u => u.providerKey)
}
}
// Delete disabled strategies
await knex('authentication').whereNotIn('key', protectedStrategies).andWhere('isEnabled', false).del()
// Update table schema
await knex.schema
.alterTable('authentication', table => {
table.integer('order').unsigned().notNullable().defaultTo(0)
table.string('strategyKey').notNullable().defaultTo('')
table.string('displayName').notNullable().defaultTo('')
})
// Fix pre-2.5 strategies
const strategies = await knex('authentication')
let idx = 1
for (const strategy of strategies) {
await knex('authentication').where('key', strategy.key).update({
strategyKey: strategy.key,
order: (strategy.key === 'local') ? 0 : idx++
})
}
}
exports.down = knex => { }

@ -1,14 +0,0 @@
const has = require('lodash/has')
exports.up = async knex => {
// -> Fix 2.5.1 added isEnabled columns for beta users
const localStrategy = await knex('authentication').where('key', 'local').first()
if (localStrategy && !has(localStrategy, 'isEnabled')) {
await knex.schema
.alterTable('authentication', table => {
table.boolean('isEnabled').notNullable().defaultTo(true)
})
}
}
exports.down = knex => { }

@ -1,6 +0,0 @@
exports.up = async knex => {
// -> Fix 2.5.117 new installations without isEnabled on local auth (#2382)
await knex('authentication').where('key', 'local').update({ isEnabled: true })
}
exports.down = knex => { }

@ -1,8 +0,0 @@
exports.up = async knex => {
await knex.schema
.alterTable('groups', table => {
table.string('redirectOnLogin').notNullable().defaultTo('/')
})
}
exports.down = knex => { }

@ -1,20 +0,0 @@
/* global WIKI */
exports.up = knex => {
const dbCompat = {
blobLength: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`),
charset: (WIKI.config.db.type === `mysql` || WIKI.config.db.type === `mariadb`)
}
return knex.schema
.createTable('userAvatars', table => {
if (dbCompat.charset) { table.charset('utf8mb4') }
table.integer('id').primary()
if (dbCompat.blobLength) {
table.specificType('data', 'LONGBLOB').notNullable()
} else {
table.binary('data').notNullable()
}
})
}
exports.down = knex => { }

@ -1,7 +0,0 @@
exports.up = async knex => {
await knex('users').update({
email: knex.raw('LOWER(??)', ['email'])
})
}
exports.down = knex => { }

@ -0,0 +1,589 @@
const { v4: uuid } = require('uuid')
const bcrypt = require('bcryptjs-then')
const crypto = require('crypto')
const pem2jwk = require('pem-jwk').pem2jwk
/* global WIKI */
exports.up = async knex => {
WIKI.logger.info('Running 3.0.0 database migration...')
// =====================================
// PG EXTENSIONS
// =====================================
await knex.raw('CREATE EXTENSION IF NOT EXISTS pgcrypto;')
await knex.schema
// =====================================
// MODEL TABLES
// =====================================
// ANALYTICS ---------------------------
.createTable('analytics', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('module').notNullable()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.jsonb('config').notNullable()
})
// API KEYS ----------------------------
.createTable('apiKeys', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('name').notNullable()
table.text('key').notNullable()
table.string('expiration').notNullable()
table.boolean('isRevoked').notNullable().defaultTo(false)
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
})
// ASSETS ------------------------------
.createTable('assets', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('filename').notNullable()
table.string('hash').notNullable().index()
table.string('ext').notNullable()
table.enum('kind', ['binary', 'image']).notNullable().defaultTo('binary')
table.string('mime').notNullable().defaultTo('application/octet-stream')
table.integer('fileSize').unsigned().comment('In kilobytes')
table.jsonb('metadata')
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
})
// ASSET DATA --------------------------
.createTable('assetData', table => {
table.uuid('id').notNullable().index()
table.binary('data').notNullable()
})
// ASSET FOLDERS -----------------------
.createTable('assetFolders', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('name').notNullable()
table.string('slug').notNullable()
})
// AUTHENTICATION ----------------------
.createTable('authentication', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('module').notNullable()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.integer('order').unsigned().notNullable().defaultTo(0)
table.string('displayName').notNullable().defaultTo('')
table.jsonb('config').notNullable().defaultTo('{}')
table.boolean('selfRegistration').notNullable().defaultTo(false)
table.jsonb('domainWhitelist').notNullable().defaultTo('[]')
table.jsonb('autoEnrollGroups').notNullable().defaultTo('[]')
table.jsonb('hideOnSites').notNullable().defaultTo('[]')
})
// COMMENTS ----------------------------
.createTable('comments', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.uuid('replyTo')
table.text('content').notNullable()
table.text('render').notNullable().defaultTo('')
table.string('name').notNullable().defaultTo('')
table.string('email').notNullable().defaultTo('')
table.string('ip').notNullable().defaultTo('')
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
})
// GROUPS ------------------------------
.createTable('groups', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('name').notNullable()
table.jsonb('permissions').notNullable()
table.jsonb('rules').notNullable()
table.string('redirectOnLogin').notNullable().defaultTo('')
table.string('redirectOnFirstLogin').notNullable().defaultTo('')
table.string('redirectOnLogout').notNullable().defaultTo('')
table.boolean('isSystem').notNullable().defaultTo(false)
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
})
// HOOKS -------------------------------
.createTable('hooks', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('name').notNullable()
table.jsonb('events').notNullable().defaultTo('[]')
table.string('url').notNullable()
table.boolean('includeMetadata').notNullable().defaultTo(false)
table.boolean('includeContent').notNullable().defaultTo(false)
table.boolean('acceptUntrusted').notNullable().defaultTo(false)
table.string('authHeader')
table.enum('state', ['pending', 'error', 'success']).notNullable().defaultTo('pending')
table.string('lastErrorMessage')
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
})
// LOCALES -----------------------------
.createTable('locales', table => {
table.string('code', 5).notNullable().primary()
table.jsonb('strings')
table.boolean('isRTL').notNullable().defaultTo(false)
table.string('name').notNullable()
table.string('nativeName').notNullable()
table.integer('availability').notNullable().defaultTo(0)
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
})
// NAVIGATION ----------------------------
.createTable('navigation', table => {
table.string('key').notNullable().primary()
table.jsonb('config')
})
// PAGE HISTORY ------------------------
.createTable('pageHistory', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.uuid('pageId').notNullable().index()
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable()
table.string('description')
table.enu('publishState', ['draft', 'published', 'scheduled']).notNullable().defaultTo('draft')
table.timestamp('publishStartDate')
table.timestamp('publishEndDate')
table.string('action').defaultTo('updated')
table.text('content')
table.string('contentType').notNullable()
table.jsonb('extra').notNullable().defaultTo('{}')
table.jsonb('tags').defaultTo('[]')
table.timestamp('versionDate').notNullable().defaultTo(knex.fn.now())
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
})
// PAGE LINKS --------------------------
.createTable('pageLinks', table => {
table.increments('id').primary()
table.string('path').notNullable()
table.string('localeCode', 5).notNullable()
})
// PAGES -------------------------------
.createTable('pages', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('slug')
table.string('path').notNullable()
table.string('hash').notNullable()
table.string('title').notNullable()
table.string('description')
table.enu('publishState', ['draft', 'published', 'scheduled']).notNullable().defaultTo('draft')
table.timestamp('publishStartDate')
table.timestamp('publishEndDate')
table.text('content')
table.text('render')
table.jsonb('toc')
table.string('contentType').notNullable()
table.jsonb('extra').notNullable().defaultTo('{}')
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
})
// PAGE TREE ---------------------------
.createTable('pageTree', table => {
table.integer('id').unsigned().primary()
table.string('path').notNullable()
table.integer('depth').unsigned().notNullable()
table.string('title').notNullable()
table.boolean('isFolder').notNullable().defaultTo(false)
table.jsonb('ancestors')
})
// RENDERERS ---------------------------
.createTable('renderers', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('module').notNullable()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.jsonb('config')
})
// SETTINGS ----------------------------
.createTable('settings', table => {
table.string('key').notNullable().primary()
table.jsonb('value')
})
// SITES -------------------------------
.createTable('sites', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('hostname').notNullable()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.jsonb('config').notNullable()
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
})
// STORAGE -----------------------------
.createTable('storage', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('module').notNullable()
table.boolean('isEnabled').notNullable().defaultTo(false)
table.jsonb('contentTypes')
table.jsonb('assetDelivery')
table.jsonb('versioning')
table.jsonb('schedule')
table.jsonb('config')
table.jsonb('state')
})
// TAGS --------------------------------
.createTable('tags', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('tag').notNullable()
table.jsonb('display').notNullable().defaultTo('{}')
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
})
// USER AVATARS ------------------------
.createTable('userAvatars', table => {
table.uuid('id').notNullable().primary()
table.binary('data').notNullable()
})
// USER KEYS ---------------------------
.createTable('userKeys', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('kind').notNullable()
table.string('token').notNullable()
table.timestamp('validUntil').notNullable()
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
})
// USERS -------------------------------
.createTable('users', table => {
table.uuid('id').notNullable().primary().defaultTo(knex.raw('gen_random_uuid()'))
table.string('email').notNullable()
table.string('name').notNullable()
table.jsonb('auth')
table.jsonb('tfa')
table.jsonb('meta')
table.jsonb('prefs')
table.string('pictureUrl')
table.boolean('isSystem').notNullable().defaultTo(false)
table.boolean('isActive').notNullable().defaultTo(false)
table.boolean('isVerified').notNullable().defaultTo(false)
table.timestamp('lastLoginAt').index()
table.timestamp('createdAt').notNullable().defaultTo(knex.fn.now())
table.timestamp('updatedAt').notNullable().defaultTo(knex.fn.now())
})
// =====================================
// RELATION TABLES
// =====================================
// PAGE TAGS ---------------------------
.createTable('pageTags', table => {
table.increments('id').primary()
table.uuid('pageId').references('id').inTable('pages').onDelete('CASCADE')
table.uuid('tagId').references('id').inTable('tags').onDelete('CASCADE')
})
// USER GROUPS -------------------------
.createTable('userGroups', table => {
table.increments('id').primary()
table.uuid('userId').references('id').inTable('users').onDelete('CASCADE')
table.uuid('groupId').references('id').inTable('groups').onDelete('CASCADE')
})
// =====================================
// REFERENCES
// =====================================
.table('analytics', table => {
table.uuid('siteId').notNullable().references('id').inTable('sites')
})
.table('assets', table => {
table.uuid('folderId').notNullable().references('id').inTable('assetFolders').index()
table.uuid('authorId').notNullable().references('id').inTable('users')
table.uuid('siteId').notNullable().references('id').inTable('sites').index()
})
.table('assetFolders', table => {
table.uuid('parentId').references('id').inTable('assetFolders').index()
})
.table('comments', table => {
table.uuid('pageId').notNullable().references('id').inTable('pages').index()
table.uuid('authorId').notNullable().references('id').inTable('users').index()
})
.table('navigation', table => {
table.uuid('siteId').notNullable().references('id').inTable('sites').index()
})
.table('pageHistory', table => {
table.string('localeCode', 5).references('code').inTable('locales')
table.uuid('authorId').notNullable().references('id').inTable('users')
table.uuid('siteId').notNullable().references('id').inTable('sites').index()
})
.table('pageLinks', table => {
table.uuid('pageId').notNullable().references('id').inTable('pages').onDelete('CASCADE')
table.index(['path', 'localeCode'])
})
.table('pages', table => {
table.string('localeCode', 5).references('code').inTable('locales').index()
table.uuid('authorId').notNullable().references('id').inTable('users').index()
table.uuid('creatorId').notNullable().references('id').inTable('users').index()
table.uuid('siteId').notNullable().references('id').inTable('sites').index()
})
.table('pageTree', table => {
table.integer('parent').unsigned().references('id').inTable('pageTree').onDelete('CASCADE')
table.uuid('pageId').notNullable().references('id').inTable('pages').onDelete('CASCADE')
table.string('localeCode', 5).references('code').inTable('locales')
})
.table('storage', table => {
table.uuid('siteId').notNullable().references('id').inTable('sites')
})
.table('tags', table => {
table.uuid('siteId').notNullable().references('id').inTable('sites')
table.unique(['siteId', 'tag'])
})
.table('userKeys', table => {
table.uuid('userId').notNullable().references('id').inTable('users')
})
.table('users', table => {
table.string('localeCode', 5).references('code').inTable('locales').notNullable().defaultTo('en')
})
// =====================================
// DEFAULT DATA
// =====================================
// -> SYSTEM CONFIG
await knex('settings').insert([
{
key: 'update',
value: {
locales: true
}
},
{
key: 'mail',
value: {
senderName: '',
senderEmail: '',
host: '',
port: 465,
secure: true,
verifySSL: true,
user: '',
pass: '',
useDKIM: false,
dkimDomainName: '',
dkimKeySelector: '',
dkimPrivateKey: ''
}
},
{
key: 'security',
value: {
corsConfig: '',
corsMode: 'OFF',
cspDirectives: '',
disallowFloc: true,
disallowIframe: true,
disallowOpenRedirect: true,
enforceCsp: false,
enforceHsts: false,
enforceSameOriginReferrerPolicy: true,
forceAssetDownload: true,
hstsDuration: 0,
trustProxy: false,
authJwtAudience: 'urn:wiki.js',
authJwtExpiration: '30m',
authJwtRenewablePeriod: '14d',
uploadMaxFileSize: 10485760,
uploadMaxFiles: 20,
uploadScanSVG: true
}
}
])
// -> DEFAULT LOCALE
await knex('locales').insert({
code: 'en',
strings: {},
isRTL: false,
name: 'English',
nativeName: 'English'
})
// -> DEFAULT SITE
WIKI.logger.info('Generating certificates...')
const secret = crypto.randomBytes(32).toString('hex')
const certs = crypto.generateKeyPairSync('rsa', {
modulusLength: 2048,
publicKeyEncoding: {
type: 'pkcs1',
format: 'pem'
},
privateKeyEncoding: {
type: 'pkcs1',
format: 'pem',
cipher: 'aes-256-cbc',
passphrase: secret
}
})
const siteId = uuid()
await knex('sites').insert({
id: siteId,
hostname: '*',
isEnabled: true,
config: {
auth: {
audience: 'urn:wiki.js',
tokenExpiration: '30m',
tokenRenewal: '14d',
certs: {
jwk: pem2jwk(certs.publicKey),
public: certs.publicKey,
private: certs.privateKey
},
secret
},
title: 'My Wiki Site',
description: '',
company: '',
contentLicense: '',
defaults: {
timezone: 'America/New_York',
dateFormat: 'YYYY-MM-DD',
timeFormat: '12h'
},
features: {
ratings: false,
ratingsMode: 'off',
comments: false,
contributions: false,
profile: true,
search: true
},
logoText: true,
robots: {
index: true,
follow: true
},
locale: 'en',
localeNamespacing: false,
localeNamespaces: [],
theme: {
dark: false,
colorPrimary: '#1976d2',
colorSecondary: '#02c39a',
colorAccent: '#f03a47',
colorHeader: '#000000',
colorSidebar: '#1976d2',
injectCSS: '',
injectHead: '',
injectBody: '',
sidebarPosition: 'left',
tocPosition: 'right',
showSharingMenu: true,
showPrintBtn: true
}
}
})
// -> DEFAULT GROUPS
const groupAdminId = uuid()
const groupGuestId = '10000000-0000-4000-0000-000000000001'
await knex('groups').insert([
{
id: groupAdminId,
name: 'Administrators',
permissions: JSON.stringify(['manage:system']),
rules: JSON.stringify([]),
isSystem: true
},
{
id: groupGuestId,
name: 'Guests',
permissions: JSON.stringify(['read:pages', 'read:assets', 'read:comments']),
rules: JSON.stringify([
{
id: uuid(),
name: 'Default Rule',
roles: ['read:pages', 'read:assets', 'read:comments'],
match: 'START',
mode: 'DENY',
path: '',
locales: [],
sites: []
}
]),
isSystem: true
}
])
// -> AUTHENTICATION MODULE
const authModuleId = uuid()
await knex('authentication').insert({
id: authModuleId,
module: 'local',
isEnabled: true,
displayName: 'Local Authentication'
})
// -> USERS
const userAdminId = uuid()
const userGuestId = uuid()
await knex('users').insert([
{
id: userAdminId,
email: process.env.ADMIN_EMAIL ?? 'admin@example.com',
auth: {
[authModuleId]: {
password: await bcrypt.hash(process.env.ADMIN_PASS || '12345678', 12),
mustChangePwd: !process.env.ADMIN_PASS,
restrictLogin: false,
tfaRequired: false,
tfaSecret: ''
}
},
name: 'Administrator',
isSystem: false,
isActive: true,
isVerified: true,
meta: {
location: '',
jobTitle: '',
pronouns: ''
},
prefs: {
timezone: 'America/New_York',
dateFormat: 'YYYY-MM-DD',
timeFormat: '12h',
darkMode: false
},
localeCode: 'en'
},
{
id: userGuestId,
email: 'guest@example.com',
name: 'Guest',
isSystem: true,
isActive: true,
isVerified: true,
localeCode: 'en'
}
])
await knex('userGroups').insert([
{
userId: userAdminId,
groupId: groupAdminId
},
{
userId: userGuestId,
groupId: groupGuestId
}
])
// -> STORAGE MODULE
await knex('storage').insert({
module: 'db',
siteId,
isEnabled: true,
contentTypes: {
activeTypes: ['pages', 'images', 'documents', 'others', 'large'],
largeThreshold: '5MB'
},
assetDelivery: {
streaming: true,
directAccess: false
},
versioning: {
enabled: false
},
state: {
current: 'ok'
}
})
WIKI.logger.info('Completed 3.0.0 database migration.')
}
exports.down = knex => { }

@ -2,7 +2,7 @@ const path = require('path')
const fs = require('fs-extra')
const semver = require('semver')
const baseMigrationPath = path.join(WIKI.SERVERPATH, (WIKI.config.db.type !== 'sqlite') ? 'db/migrations' : 'db/migrations-sqlite')
const baseMigrationPath = path.join(WIKI.SERVERPATH, 'db/migrations')
/* global WIKI */

@ -1,13 +1,185 @@
const graphHelper = require('../../helpers/graph')
const _ = require('lodash')
const CleanCSS = require('clean-css')
const path = require('path')
/* global WIKI */
module.exports = {
Query: {
async sites () {
const sites = await WIKI.models.sites.query()
return sites.map(s => ({
...s.config,
id: s.id,
hostname: s.hostname,
isEnabled: s.isEnabled
}))
},
async siteById (obj, args) {
const site = await WIKI.models.sites.query().findById(args.id)
return site ? {
...site.config,
id: site.id,
hostname: site.hostname,
isEnabled: site.isEnabled
} : null
},
async siteByHostname (obj, args) {
let site = await WIKI.models.sites.query().where({
hostname: args.hostname
}).first()
if (!site && !args.exact) {
site = await WIKI.models.sites.query().where({
hostname: '*'
}).first()
}
return site ? {
...site.config,
id: site.id,
hostname: site.hostname,
isEnabled: site.isEnabled
} : null
},
// LEGACY
async site() { return {} }
},
Mutation: {
/**
* CREATE SITE
*/
async createSite (obj, args) {
try {
// -> Validate inputs
if (!args.hostname || args.hostname.length < 1 || !/^(\\*)|([a-z0-9\-.:]+)$/.test(args.hostname)) {
throw WIKI.ERROR(new Error('Invalid Site Hostname'), 'SiteCreateInvalidHostname')
}
if (!args.title || args.title.length < 1 || !/^[^<>"]+$/.test(args.title)) {
throw WIKI.ERROR(new Error('Invalid Site Title'), 'SiteCreateInvalidTitle')
}
// -> Check for duplicate catch-all
if (args.hostname === '*') {
const site = await WIKI.models.sites.query().where({
hostname: args.hostname
}).first()
if (site) {
throw WIKI.ERROR(new Error('A site with a catch-all hostname already exists! Cannot have 2 catch-all hostnames.'), 'SiteCreateDuplicateCatchAll')
}
}
// -> Create site
const newSite = await WIKI.models.sites.createSite(args.hostname, {
title: args.title
})
return {
status: graphHelper.generateSuccess('Site created successfully'),
site: newSite
}
} catch (err) {
return graphHelper.generateError(err)
}
},
/**
* UPDATE SITE
*/
async updateSite (obj, args) {
try {
// -> Load site
const site = await WIKI.models.sites.query().findById(args.id)
if (!site) {
throw WIKI.ERROR(new Error('Invalid Site ID'), 'SiteInvalidId')
}
// -> Check for bad input
if (_.has(args.patch, 'hostname') && _.trim(args.patch.hostname).length < 1) {
throw WIKI.ERROR(new Error('Hostname is invalid.'), 'SiteInvalidHostname')
}
// -> Check for duplicate catch-all
if (args.patch.hostname === '*' && site.hostname !== '*') {
const dupSite = await WIKI.models.sites.query().where({ hostname: '*' }).first()
if (dupSite) {
throw WIKI.ERROR(new Error(`Site ${dupSite.config.title} with a catch-all hostname already exists! Cannot have 2 catch-all hostnames.`), 'SiteUpdateDuplicateCatchAll')
}
}
// -> Format Code
if (args.patch?.theme?.injectCSS) {
args.patch.theme.injectCSS = new CleanCSS({ inline: false }).minify(args.patch.theme.injectCSS).styles
}
// -> Update site
await WIKI.models.sites.updateSite(args.id, {
hostname: args.patch.hostname ?? site.hostname,
isEnabled: args.patch.isEnabled ?? site.isEnabled,
config: _.defaultsDeep(_.omit(args.patch, ['hostname', 'isEnabled']), site.config)
})
return {
status: graphHelper.generateSuccess('Site updated successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
}
},
/**
* DELETE SITE
*/
async deleteSite (obj, args) {
try {
// -> Ensure site isn't last one
const sitesCount = await WIKI.models.sites.query().count('id').first()
if (sitesCount?.count && _.toNumber(sitesCount?.count) <= 1) {
throw WIKI.ERROR(new Error('Cannot delete the last site. At least 1 site must exists at all times.'), 'SiteDeleteLastSite')
}
// -> Delete site
await WIKI.models.sites.deleteSite(args.id)
return {
status: graphHelper.generateSuccess('Site deleted successfully')
}
} catch (err) {
WIKI.logger.warn(err)
return graphHelper.generateError(err)
}
},
/**
* UPLOAD LOGO
*/
async uploadSiteLogo (obj, args) {
try {
const { filename, mimetype, createReadStream } = await args.image
WIKI.logger.info(`Processing site logo ${filename} of type ${mimetype}...`)
if (!WIKI.extensions.ext.sharp.isInstalled) {
throw new Error('This feature requires the Sharp extension but it is not installed.')
}
console.info(mimetype)
const destFormat = mimetype.startsWith('image/svg') ? 'svg' : 'png'
const destPath = path.resolve(
process.cwd(),
WIKI.config.dataPath,
`assets/logo.${destFormat}`
)
await WIKI.extensions.ext.sharp.resize({
format: destFormat,
inputStream: createReadStream(),
outputPath: destPath,
width: 100
})
WIKI.logger.info('New site logo processed successfully.')
return {
status: graphHelper.generateSuccess('Site logo uploaded successfully')
}
} catch (err) {
return graphHelper.generateError(err)
}
},
/**
* UPLOAD FAVICON
*/
async uploadSiteFavicon (obj, args) {
const { filename, mimetype, createReadStream } = await args.image
console.info(filename, mimetype)
return {
status: graphHelper.generateSuccess('Site favicon uploaded successfully')
}
},
// LEGACY
async site() { return {} }
},
SiteQuery: {

@ -0,0 +1,59 @@
const { Kind, GraphQLScalarType } = require('graphql')
function ensureObject (value) {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
throw new TypeError(`JSONObject cannot represent non-object value: ${value}`)
}
return value
}
function parseLiteral (typeName, ast, variables) {
switch (ast.kind) {
case Kind.STRING:
case Kind.BOOLEAN:
return ast.value
case Kind.INT:
case Kind.FLOAT:
return parseFloat(ast.value)
case Kind.OBJECT:
return parseObject(typeName, ast, variables)
case Kind.LIST:
return ast.values.map((n) => parseLiteral(typeName, n, variables))
case Kind.NULL:
return null
case Kind.VARIABLE:
return variables ? variables[ast.name.value] : undefined
default:
throw new TypeError(`${typeName} cannot represent value: ${ast}`)
}
}
function parseObject (typeName, ast, variables) {
const value = Object.create(null)
ast.fields.forEach((field) => {
// eslint-disable-next-line no-use-before-define
value[field.name.value] = parseLiteral(typeName, field.value, variables)
})
return value
}
module.exports = {
JSON: new GraphQLScalarType({
name: 'JSON',
description:
'The `JSON` scalar type represents JSON objects as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf).',
specifiedByUrl:
'http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf',
serialize: ensureObject,
parseValue: ensureObject,
parseLiteral: (ast, variables) => {
if (ast.kind !== Kind.OBJECT) {
throw new TypeError(`JSONObject cannot represent non-object value: ${ast}`)
}
return parseObject('JSONObject', ast, variables)
}
})
}

@ -0,0 +1,39 @@
const { Kind, GraphQLScalarType } = require('graphql')
// const { Kind } = require('graphql/language')
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i
const nilUUID = '00000000-0000-0000-0000-000000000000'
function isUUID (value) {
return uuidRegex.test(value) || nilUUID === value
}
module.exports = {
UUID: new GraphQLScalarType({
name: 'UUID',
description: 'The `UUID` scalar type represents UUID values as specified by [RFC 4122](https://tools.ietf.org/html/rfc4122).',
serialize: (value) => {
if (!isUUID(value)) {
throw new TypeError(`UUID cannot represent non-UUID value: ${value}`)
}
return value.toLowerCase()
},
parseValue: (value) => {
if (!isUUID(value)) {
throw new TypeError(`UUID cannot represent non-UUID value: ${value}`)
}
return value.toLowerCase()
},
parseLiteral: (ast) => {
if (ast.kind === Kind.STRING) {
if (isUUID(ast.value)) {
return ast.value
}
}
return undefined
}
})
}

@ -34,6 +34,11 @@ type ResponseStatus {
message: String
}
enum OrderByDirection {
asc
desc
}
# ROOT
# ----

@ -1,3 +1,6 @@
# SCALARS
scalar Date
scalar JSON
# scalar Upload
scalar UUID

@ -1,12 +1,49 @@
# ===============================================
# SITE
# SITES
# ===============================================
extend type Query {
sites: [Site] @auth(requires: ["manage:system"])
siteById (
id: UUID!
): Site @auth(requires: ["manage:system"])
siteByHostname (
hostname: String!
exact: Boolean!
): Site @auth(requires: ["manage:system"])
# Legacy
site: SiteQuery
}
extend type Mutation {
createSite (
hostname: String!
title: String!
): SiteCreateResponse @auth(requires: ["manage:system"])
updateSite (
id: UUID!
patch: SiteUpdateInput!
): DefaultResponse @auth(requires: ["manage:system"])
uploadSiteLogo (
id: UUID!
image: Upload!
): DefaultResponse @auth(requires: ["manage:system"])
uploadSiteFavicon (
id: UUID!
image: Upload!
): DefaultResponse @auth(requires: ["manage:system"])
deleteSite (
id: UUID!
): DefaultResponse @auth(requires: ["manage:system"])
# Legacy
site: SiteMutation
}
@ -64,6 +101,135 @@ type SiteMutation {
# TYPES
# -----------------------------------------------
type Site {
id: UUID
hostname: String
isEnabled: Boolean
title: String
description: String
company: String
contentLicense: String
logoText: Boolean
robots: SiteRobots
features: SiteFeatures
defaults: SiteDefaults
locale: String
localeNamespaces: [String]
localeNamespacing: Boolean
theme: SiteTheme
}
type SiteRobots {
index: Boolean
follow: Boolean
}
type SiteFeatures {
ratings: Boolean
ratingsMode: SitePageRatingModes
comments: Boolean
contributions: Boolean
profile: Boolean
search: Boolean
}
type SiteDefaults {
timezone: String
dateFormat: String
timeFormat: String
}
type SiteLocale {
locale: String
autoUpdate: Boolean
namespacing: Boolean
namespaces: [String]
}
type SiteTheme {
dark: Boolean
colorPrimary: String
colorSecondary: String
colorAccent: String
colorHeader: String
colorSidebar: String
injectCSS: String
injectHead: String
injectBody: String
sidebarPosition: SiteThemePosition
tocPosition: SiteThemePosition
showSharingMenu: Boolean
showPrintBtn: Boolean
}
enum SiteThemePosition {
left
right
}
enum SitePageRatingModes {
off
thumbs
stars
}
type SiteCreateResponse {
status: ResponseStatus
site: Site
}
input SiteUpdateInput {
hostname: String
isEnabled: Boolean
title: String
description: String
company: String
contentLicense: String
logoText: Boolean
robots: SiteRobotsInput
features: SiteFeaturesInput
defaults: SiteDefaultsInput
theme: SiteThemeInput
}
input SiteRobotsInput {
index: Boolean
follow: Boolean
}
input SiteFeaturesInput {
ratings: Boolean
ratingsMode: SitePageRatingModes
comments: Boolean
contributions: Boolean
profile: Boolean
search: Boolean
}
input SiteDefaultsInput {
timezone: String
dateFormat: String
timeFormat: String
}
input SiteThemeInput {
dark: Boolean
colorPrimary: String
colorSecondary: String
colorAccent: String
colorHeader: String
colorSidebar: String
injectCSS: String
injectHead: String
injectBody: String
sidebarPosition: SiteThemePosition
tocPosition: SiteThemePosition
showSharingMenu: Boolean
showPrintBtn: Boolean
}
# LEGACY
type SiteConfig {
host: String
title: String

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

@ -0,0 +1,6 @@
/dist
/src-capacitor
/src-cordova
/.quasar
/node_modules
.eslintrc.js

@ -0,0 +1,75 @@
module.exports = {
// https://eslint.org/docs/user-guide/configuring#configuration-cascading-and-hierarchy
// This option interrupts the configuration hierarchy at this file
// Remove this if you have an higher level ESLint config file (it usually happens into a monorepos)
root: true,
parserOptions: {
ecmaVersion: '2021' // Allows for the parsing of modern ECMAScript features
},
env: {
node: true,
browser: true,
'vue/setup-compiler-macros': true
},
// Rules order is important, please avoid shuffling them
extends: [
// Base ESLint recommended rules
// 'eslint:recommended',
// Uncomment any of the lines below to choose desired strictness,
// but leave only one uncommented!
// See https://eslint.vuejs.org/rules/#available-rules
'plugin:vue/vue3-essential', // Priority A: Essential (Error Prevention)
'plugin:vue/vue3-strongly-recommended', // Priority B: Strongly Recommended (Improving Readability)
// 'plugin:vue/vue3-recommended', // Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead)
'standard'
],
plugins: [
// https://eslint.vuejs.org/user-guide/#why-doesn-t-it-work-on-vue-files
// required to lint *.vue files
'vue'
],
globals: {
ga: 'readonly', // Google Analytics
__statics: 'readonly',
__QUASAR_SSR__: 'readonly',
__QUASAR_SSR_SERVER__: 'readonly',
__QUASAR_SSR_CLIENT__: 'readonly',
__QUASAR_SSR_PWA__: 'readonly',
process: 'readonly',
APOLLO_CLIENT: 'readonly'
},
// add your custom rules here
rules: {
// allow async-await
'generator-star-spacing': 'off',
// allow paren-less arrow functions
'arrow-parens': 'off',
'one-var': 'off',
'no-void': 'off',
'multiline-ternary': 'off',
'import/first': 'off',
'import/named': 'error',
'import/namespace': 'error',
'import/default': 'error',
'import/export': 'error',
'import/extensions': 'off',
'import/no-unresolved': 'off',
'import/no-extraneous-dependencies': 'off',
'prefer-promise-reject-errors': 'off',
'no-unused-vars': 'off',
// allow debugger during development only
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
}
}

29
ux/.gitignore vendored

@ -0,0 +1,29 @@
.DS_Store
.thumbs.db
node_modules
# Quasar core related directories
.quasar
/dist
# Cordova related directories and files
/src-cordova/node_modules
/src-cordova/platforms
/src-cordova/plugins
/src-cordova/www
# Capacitor related directories and files
/src-capacitor/www
/src-capacitor/node_modules
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln

@ -0,0 +1,14 @@
{
"recommendations": [
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"johnsoncodehk.volar",
"wayou.vscode-todo-highlight"
],
"unwantedRecommendations": [
"octref.vetur",
"hookyqr.beautify",
"dbaeumer.jshint",
"ms-vscode.vscode-typescript-tslint-plugin"
]
}

@ -0,0 +1,16 @@
{
"editor.bracketPairColorization.enabled": true,
"editor.guides.bracketPairs": true,
"editor.formatOnSave": true,
"editor.defaultFormatter": "dbaeumer.vscode-eslint",
"editor.codeActionsOnSave": [
"source.fixAll.eslint"
],
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"vue"
],
"i18n-ally.localesPaths": "src/i18n/locales"
}

@ -0,0 +1,33 @@
# Wiki.js (ux)
The most powerful and extensible open source Wiki software
## Install the dependencies
```bash
yarn
# or
npm install
```
### Start the app in development mode (hot-code reloading, error reporting, etc.)
```bash
quasar dev
```
### Lint the files
```bash
yarn lint
# or
npm run lint
```
### Build the app for production
```bash
quasar build
```
### Customize the configuration
See [Configuring quasar.config.js](https://v2.quasar.dev/quasar-cli-vite/quasar-config-js).

@ -0,0 +1,14 @@
module.exports = {
client: {
service: {
name: 'wiki-core',
// URL to the GraphQL API
url: 'http://localhost:11511'
},
// Files processed by the extension
includes: [
'src/**/*.vue',
'src/**/*.js'
]
}
}

@ -0,0 +1,74 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="format-detection" content="telephone=no">
<meta name="msapplication-tap-highlight" content="no">
<meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width">
<title>Wiki.js</title>
<!--preload-links-->
<style type="text/css">
@keyframes initspinner {
to { transform: rotate(360deg); }
}
.init-loading {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(255,255,255,.75);
z-index: 2000000000;
backdrop-filter: blur(10px);
}
.body--dark .init-loading {
background-color: rgba(0,0,0,.75);
}
.init-loading:before {
content: '';
box-sizing: border-box;
position: absolute;
top: 50%;
left: 50%;
width: 50px;
height: 50px;
margin-top: -25px;
margin-left: -25px;
border-radius: 50%;
border-top: 2px solid #1976D2;
border-right: 2px solid transparent;
animation: initspinner .6s linear infinite;
z-index: 2000000000;
}
</style>
<noscript>
<style type="text/css">
.init-loading {
display: none !important;
}
.q-drawer-container {
display: none !important;
}
.scroll.relative-position {
position: static !important;
}
.scroll.relative-position > .absolute {
position: relative !important;
}
</style>
</noscript>
</head>
<body>
<div class="init-loading"></div>
<div id="app"><!-- quasar:entry-point --></div>
<script type="module" src="/entry-client.js"></script>
</body>
</html>

@ -0,0 +1,39 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"src/*": [
"src/*"
],
"app/*": [
"*"
],
"components/*": [
"src/components/*"
],
"layouts/*": [
"src/layouts/*"
],
"pages/*": [
"src/pages/*"
],
"assets/*": [
"src/assets/*"
],
"boot/*": [
"src/boot/*"
],
"stores/*": [
"src/stores/*"
],
"vue$": [
"node_modules/vue/dist/vue.runtime.esm-bundler.js"
]
}
},
"exclude": [
"dist",
".quasar",
"node_modules"
]
}

@ -0,0 +1,102 @@
{
"name": "ux",
"version": "0.0.1",
"description": "The most powerful and extensible open source Wiki software",
"productName": "Wiki.js",
"author": "Nicolas Giard <nick@requarks.io>",
"private": true,
"scripts": {
"dev": "quasar dev",
"build": "quasar build",
"lint": "eslint --ext .js,.vue ./"
},
"dependencies": {
"@apollo/client": "3.5.10",
"@codemirror/autocomplete": "0.19.15",
"@codemirror/basic-setup": "0.19.1",
"@codemirror/closebrackets": "0.19.1",
"@codemirror/commands": "0.19.8",
"@codemirror/comment": "0.19.1",
"@codemirror/fold": "0.19.3",
"@codemirror/gutter": "0.19.9",
"@codemirror/highlight": "0.19.8",
"@codemirror/history": "0.19.2",
"@codemirror/lang-css": "0.19.3",
"@codemirror/lang-html": "0.19.4",
"@codemirror/lang-javascript": "0.19.7",
"@codemirror/lang-json": "0.19.2",
"@codemirror/lang-markdown": "0.19.6",
"@codemirror/matchbrackets": "0.19.4",
"@codemirror/search": "0.19.9",
"@codemirror/state": "0.19.9",
"@codemirror/tooltip": "0.19.16",
"@codemirror/view": "0.19.47",
"@lezer/common": "0.15.12",
"@quasar/extras": "1.13.5",
"@tiptap/core": "2.0.0-beta.174",
"@tiptap/extension-code-block": "2.0.0-beta.37",
"@tiptap/extension-code-block-lowlight": "2.0.0-beta.68",
"@tiptap/extension-color": "2.0.0-beta.9",
"@tiptap/extension-dropcursor": "2.0.0-beta.25",
"@tiptap/extension-font-family": "2.0.0-beta.21",
"@tiptap/extension-gapcursor": "2.0.0-beta.34",
"@tiptap/extension-hard-break": "2.0.0-beta.30",
"@tiptap/extension-highlight": "2.0.0-beta.33",
"@tiptap/extension-history": "2.0.0-beta.21",
"@tiptap/extension-image": "2.0.0-beta.27",
"@tiptap/extension-mention": "2.0.0-beta.95",
"@tiptap/extension-placeholder": "2.0.0-beta.48",
"@tiptap/extension-table": "2.0.0-beta.48",
"@tiptap/extension-table-cell": "2.0.0-beta.20",
"@tiptap/extension-table-header": "2.0.0-beta.22",
"@tiptap/extension-table-row": "2.0.0-beta.19",
"@tiptap/extension-task-item": "2.0.0-beta.31",
"@tiptap/extension-task-list": "2.0.0-beta.26",
"@tiptap/extension-text-align": "2.0.0-beta.29",
"@tiptap/extension-text-style": "2.0.0-beta.23",
"@tiptap/extension-typography": "2.0.0-beta.20",
"@tiptap/starter-kit": "2.0.0-beta.183",
"@tiptap/vue-3": "2.0.0-beta.90",
"@vue/apollo-option": "4.0.0-alpha.16",
"apollo-upload-client": "17.0.0",
"browser-fs-access": "0.26.1",
"clipboard": "2.0.10",
"filesize": "8.0.7",
"filesize-parser": "1.5.0",
"graphql": "16.3.0",
"graphql-tag": "2.12.6",
"js-cookie": "3.0.1",
"jwt-decode": "3.1.2",
"lodash": "4.17.21",
"luxon": "2.3.1",
"pinia": "2.0.13",
"pug": "3.0.2",
"quasar": "2.6.5",
"tippy.js": "6.3.7",
"uuid": "8.3.2",
"v-network-graph": "0.5.9",
"vue": "3.2.31",
"vue-i18n": "9.1.9",
"vue-router": "4.0.14",
"vuedraggable": "4.1.0",
"zxcvbn": "4.4.2"
},
"devDependencies": {
"@intlify/vite-plugin-vue-i18n": "3.4.0",
"@quasar/app-vite": "1.0.0-beta.13",
"@types/lodash": "4.14.181",
"autoprefixer": "10.4.4",
"eslint": "8.12.0",
"eslint-config-standard": "17.0.0-1",
"eslint-plugin-import": "2.26.0",
"eslint-plugin-n": "15.1.0",
"eslint-plugin-promise": "6.0.0",
"eslint-plugin-vue": "8.6.0"
},
"engines": {
"node": "^18 || ^16",
"npm": ">= 6.13.4",
"yarn": ">= 1.21.1"
},
"eslint.packageManager": "yarn"
}

@ -0,0 +1,27 @@
/* eslint-disable */
// https://github.com/michael-ciniawsky/postcss-load-config
module.exports = {
plugins: [
// https://github.com/postcss/autoprefixer
require('autoprefixer')({
overrideBrowserslist: [
'last 4 Chrome versions',
'last 4 Firefox versions',
'last 4 Edge versions',
'last 4 Safari versions',
'last 4 Android versions',
'last 4 ChromeAndroid versions',
'last 4 FirefoxAndroid versions',
'last 4 iOS versions'
]
})
// https://github.com/elchininet/postcss-rtlcss
// If you want to support RTL css, then
// 1. yarn/npm install postcss-rtlcss
// 2. optionally set quasar.config.js > framework > lang to an RTL language
// 3. uncomment the following line:
// require('postcss-rtlcss')
]
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 578 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 627 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#546E7A" d="M5 22H7V26H5zM41 22H43V26H41z"/><path fill="#FFC107" d="M40,19H8v20c0,2.209,1.791,4,4,4h24c2.209,0,4-1.791,4-4V19z"/><path fill="#FFECB3" d="M36,31H12v-2c0,0,0-1,1-1s21,0,22,0s1,1,1,1V31z"/><path fill="#37474F" d="M10,19v1c0,1.104,0.896,2,2,2h24c1.104,0,2-0.896,2-2v-1H10z"/><path fill="#37474F" d="M28.619 19h-9.238C17.924 19.733 17 20.804 17 22c0 2.209 3.134 4 7 4s7-1.791 7-4C31 20.804 30.076 19.733 28.619 19zM36 38c0 1.104-.896 2-2 2H14c-1.104 0-2-.896-2-2v-7h24V38zM7 23H8V25H7zM40 23H41V25H40z"/><g><path fill="#90CAF9" d="M12 5H36V20H12z"/><path fill="#90CAF9" d="M24 18.57A3 1.715 0 1 0 24 22A3 1.715 0 1 0 24 18.57Z"/></g><g><path fill="#1976D2" d="M16 9H32V11H16zM16 13H28V15H16z"/></g><g><path fill="#B0BEC5" d="M15 33A1 1 0 1 0 15 35 1 1 0 1 0 15 33zM15 36A1 1 0 1 0 15 38 1 1 0 1 0 15 36zM33 33A1 1 0 1 0 33 35 1 1 0 1 0 33 33zM33 36A1 1 0 1 0 33 38 1 1 0 1 0 33 36zM18 33A1 1 0 1 0 18 35 1 1 0 1 0 18 33zM21 33A1 1 0 1 0 21 35 1 1 0 1 0 21 33zM24 33A1 1 0 1 0 24 35 1 1 0 1 0 24 33zM27 33A1 1 0 1 0 27 35 1 1 0 1 0 27 33zM30 33A1 1 0 1 0 30 35 1 1 0 1 0 30 33zM31 37c0 .553-.447 1-1 1H18c-.552 0-1-.447-1-1l0 0c0-.553.448-1 1-1h12C30.553 36 31 36.447 31 37L31 37z"/></g></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#388e3c" d="M17.204 19.122l-4.907 2.715C12.113 21.938 12 22.126 12 22.329v5.433c0 .203.113.39.297.492l4.908 2.717c.183.101.41.101.593 0l4.907-2.717C22.887 28.152 23 27.965 23 27.762v-5.433c0-.203-.113-.39-.297-.492l-4.906-2.715c-.092-.051-.195-.076-.297-.076-.103 0-.205.025-.297.076M42.451 24.013l-.818.452c-.031.017-.049.048-.049.082v.906c0 .034.019.065.049.082l.818.453c.031.017.068.017.099 0l.818-.453c.03-.017.049-.048.049-.082v-.906c0-.034-.019-.065-.05-.082l-.818-.452C42.534 24.004 42.517 24 42.5 24S42.466 24.004 42.451 24.013"/><path fill="#37474f" d="M35.751,13.364l-2.389-1.333c-0.075-0.042-0.167-0.041-0.241,0.003 c-0.074,0.044-0.12,0.123-0.12,0.209L33,20.295l-2.203-1.219C30.705,19.025,30.602,19,30.5,19c-0.102,0-0.205,0.025-0.297,0.076 h0.001l-4.907,2.715C25.113,21.892,25,22.08,25,22.282v5.433c0,0.203,0.113,0.39,0.297,0.492l4.908,2.717 c0.183,0.101,0.41,0.101,0.593,0l4.907-2.717C35.887,28.106,36,27.918,36,27.715V13.788C36,13.612,35.904,13.45,35.751,13.364z M32.866,26.458l-2.23,1.235c-0.083,0.046-0.186,0.046-0.269,0l-2.231-1.235C28.051,26.412,28,26.326,28,26.234v-2.47 c0-0.092,0.051-0.177,0.135-0.224l2.231-1.234h-0.001c0.042-0.023,0.088-0.034,0.135-0.034c0.047,0,0.093,0.012,0.135,0.034 l2.23,1.234C32.949,23.587,33,23.673,33,23.765v2.47C33,26.326,32.949,26.412,32.866,26.458z"/><path fill="#2e7d32" d="M17.204,19.122L12,27.762c0,0.203,0.113,0.39,0.297,0.492l4.908,2.717 c0.183,0.101,0.41,0.101,0.593,0L23,22.329c0-0.203-0.113-0.39-0.297-0.492l-4.906-2.715c-0.092-0.051-0.195-0.076-0.297-0.076 c-0.103,0-0.205,0.025-0.297,0.076"/><path fill="#4caf50" d="M17.204,19.122l-4.907,2.715C12.113,21.938,12,22.126,12,22.329l5.204,8.642 c0.183,0.101,0.41,0.101,0.593,0l4.907-2.717C22.887,28.152,23,27.965,23,27.762l-5.203-8.64c-0.092-0.051-0.195-0.076-0.297-0.076 c-0.103,0-0.205,0.025-0.297,0.076"/><path fill="#37474f" d="M47.703 21.791l-4.906-2.715C42.705 19.025 42.602 19 42.5 19c-.102 0-.205.025-.297.076h.001l-4.907 2.715C37.114 21.892 37 22.084 37 22.294v5.411c0 .209.114.402.297.503l4.908 2.717c.184.102.409.102.593 0l2.263-1.253c.207-.115.206-.412-.002-.526l-4.924-2.687C40.052 26.412 40 26.325 40 26.231v-2.466c0-.092.05-.177.13-.221l2.235-1.236h-.001c.042-.023.088-.034.135-.034.047 0 .093.012.135.034l2.235 1.237c.08.044.13.129.13.221v2.012c0 .086.046.166.121.209.075.042.167.042.242-.001l2.398-1.393c.148-.086.24-.245.24-.417v-1.88C48 22.085 47.886 21.892 47.703 21.791zM10.703 21.791l-4.906-2.715C5.705 19.025 5.602 19 5.5 19c-.102 0-.205.025-.297.076h.001l-4.907 2.715C.114 21.892 0 22.084 0 22.294v7.465c0 .086.046.166.121.209.075.042.167.042.242-.001l2.398-1.393C2.909 28.488 3 28.329 3 28.157v-4.393c0-.092.05-.177.13-.221l2.235-1.236H5.365c.042-.023.088-.034.135-.034.047 0 .093.012.135.034l2.235 1.237C7.95 23.588 8 23.673 8 23.765v4.393c0 .172.091.331.24.417l2.398 1.393c.075.043.167.043.242.001C10.954 29.925 11 29.845 11 29.759v-7.464C11 22.085 10.886 21.892 10.703 21.791z"/></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#fff" d="M44.083,29.79c-0.183-0.829-0.935-1.796-2.452-1.796c-0.31,0-0.649,0.039-1.035,0.119c-0.708,0.146-1.311,0.217-1.842,0.241c4.133-7.04,6.816-16.819,4.159-20.214c-3.501-4.473-8.214-5.141-10.711-5.141L31.967,3c-0.929,0.015-1.893,0.129-2.863,0.339l-3.583,0.774C25.033,4.052,24.536,4.009,24.018,4l-0.03,0l-0.016,0l-0.152-0.001c-1.593,0-3.046,0.338-4.341,0.973l-1.251-0.493c-1.72-0.678-4.308-1.485-6.868-1.485c-0.144,0-0.287,0.003-0.431,0.008C8.407,3.093,6.241,4.05,4.664,5.769C2.696,7.915,1.8,11.054,2.003,15.1C2.013,15.309,4.461,36,11.4,36h0.025l0.064-0.001c0.901-0.022,1.76-0.384,2.563-1.077c0.613,0.46,1.406,0.732,2.145,0.84c0.488,0.115,1.366,0.278,2.418,0.278c1.284,0,2.442-0.263,3.44-0.738c-0.001,0.88-0.006,1.994-0.016,3.418l-0.001,0.075l0.005,0.075c0.097,1.419,0.342,2.698,0.711,3.701c1.051,2.859,2.866,4.434,5.111,4.434c0.093,0,0.188-0.003,0.284-0.009c1.846-0.114,3.717-1.151,5.004-2.772c1.393-1.755,1.715-3.607,1.839-5.026L35,39.111v-0.088v-4.079l0.103,0.01l0.436,0.038l0.042,0.004l0.042,0.002c0.124,0.006,0.252,0.008,0.381,0.008c1.507,0,3.362-0.391,4.616-0.974C41.819,33.476,44.559,31.948,44.083,29.79z"/><path fill="#0277bd" d="M33,34c0-0.205,0.012-0.376,0.018-0.565C33.008,33.184,33,33,33,33s0.012-0.009,0.032-0.022c0.149-2.673,0.886-3.703,1.675-4.29c-0.11-0.153-0.237-0.318-0.356-0.475c-0.333-0.437-0.748-0.979-1.192-1.674l-0.082-0.158c-0.067-0.164-0.229-0.447-0.435-0.819c-1.183-2.14-3.645-6.592-1.96-9.404c0.738-1.232,2.122-1.942,4.121-2.117C33.986,11.718,30.925,6.115,23.985,6c-0.002,0-0.004,0-0.006,0c-6.041-0.098-8.026,5.392-8.672,8.672c0.89-0.377,1.906-0.606,2.836-0.606c0.014,0,0.029,0,0.043,0c2.29,0.017,3.865,1.239,4.323,3.354c0.335,1.552,0.496,2.91,0.492,4.153c-0.01,2.719-0.558,4.149-1.042,5.411l-0.154,0.408c-0.124,0.334-0.255,0.645-0.379,0.937c-0.126,0.298-0.237,0.563-0.318,0.802c0.484,0.11,0.864,0.265,1.125,0.38l0.151,0.066c0.047,0.02,0.094,0.043,0.137,0.069c0.848,0.516,1.376,1.309,1.489,2.233c0.061,0.498,0.051,3.893,0.03,6.855c0.087,1.285,0.305,2.364,0.593,3.146c0.409,1.114,1.431,3.241,3.394,3.119c1.37-0.085,2.687-0.919,3.561-2.019c0.938-1.181,1.284-2.487,1.414-3.958V34z"/><path fill="#0277bd" d="M15.114 28.917c-1.613-1.683-2.399-3.947-2.104-6.056.285-2.035.124-4.027.037-5.098-.029-.357-.048-.623-.047-.77 0-.008.002-.015.003-.023 0-.004-.002-.007-.002-.011.121-3.021 1.286-7.787 4.493-10.62C15.932 5.724 13.388 4.913 11 5 7.258 5.136 3.636 7.724 4 15c.137 2.73 3.222 19.103 7.44 19 .603-.015 1.229-.402 1.872-1.176 1.017-1.223 2.005-2.332 2.708-3.104C15.705 29.481 15.401 29.217 15.114 28.917zM37.023 14.731c.015.154.002.286-.022.408.031.92-.068 1.813-.169 2.677-.074.636-.15 1.293-.171 1.952-.021.645.07 1.282.166 1.956.225 1.578.459 3.359-.765 5.437.225.296.423.571.581.837 4.61-7.475 6.468-16.361 4.695-18.626C38.655 5.944 34.941 4.952 31.999 5c-.921.015-1.758.139-2.473.294C34.602 7.754 36.863 13.026 37.023 14.731zM41 30.071c-2.665.55-3.947.257-4.569-.126-.1.072-.2.133-.293.19-.372.225-.961.583-1.105 2.782.083.016.156.025.246.044L35.714 33c1.32.06 3.049-.31 4.063-.781C41.962 31.205 43.153 29.627 41 30.071zM22.023 32.119c-.037-.298-.198-.539-.492-.732l-.108-.047C21.062 31.181 20.653 31 20 31h-.004c-.127.01-.253.019-.38.019-.052 0-.103-.007-.155-.009-.474.365-1.148.647-2.816.99-2.98.759-1.221 1.655-.078 1.794 1.106.277 3.735.614 5.481-.809C22.043 32.537 22.035 32.229 22.023 32.119z"/><path fill="#0277bd" d="M20.681 18.501c-.292.302-.753.566-1.262.484-.828-.134-1.463-1.133-1.417-1.508h0c.044-.374.751-.569 1.578-.435.287.047.548.128.768.228-.32-.688-.899-1.085-1.782-1.182-1.565-.174-3.226.644-3.56 1.097.007.11.02.251.033.417.093 1.147.265 3.284-.05 5.537-.208 1.485.393 3.169 1.567 4.395.757.79 1.641 1.29 2.513 1.438.111-.478.309-.944.513-1.425.113-.265.233-.547.346-.852l.162-.427c.443-1.155.9-2.35.909-4.703C21.003 20.66 20.892 19.627 20.681 18.501zM34.847 22.007c-.104-.729-.211-1.484-.185-2.303.023-.742.105-1.442.184-2.119.062-.533.11-1.045.138-1.55-1.289.107-2.145.479-2.551 1.108.168-.057.358-.102.568-.129.892-.116 1.543.141 1.618.637.055.363-.253.705-.388.836-.277.269-.626.442-.981.488-.064.008-.129.012-.192.012-.353 0-.69-.121-.949-.3.112 1.973 1.567 4.612 2.283 5.907.153.277.271.498.369.688C35.154 24.163 35.009 23.143 34.847 22.007z"/></svg>

After

Width:  |  Height:  |  Size: 4.2 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><path fill="#33c481" d="M24,30.571c-8.837,0-16,4.921-16,10.286V42c0,1.105,0.895,2,2,2h28c1.105,0,2-0.895,2-2v-1.143 C40,35.492,32.837,30.571,24,30.571z"/><path fill="#21a366" d="M30,32.6c0-0.37,0-0.788,0-1.232c-1.854-0.506-3.876-0.796-6-0.796s-4.146,0.29-6,0.796 c0,0.445,0,0.863,0,1.232c0,2.277,6,8.4,6,8.4S30,34.876,30,32.6z"/><path fill="#d6a121" d="M29,32c0,1.897-5,7-5,7s-5-5.103-5-7c0-2.637,0-8.035,0-8.035h10C29,23.965,29,29.363,29,32z"/><path fill="#fff" d="M29,32c0,1.897-5,7-5,7s-5-5.103-5-7c0,0,2,2,5,2S29,32,29,32z"/><linearGradient id="DVu_EwlVcyi3M2~gQlPREa" x1="32.917" x2="34.251" y1="20" y2="20" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#c48f0c"/><stop offset=".251" stop-color="#d19b16"/><stop offset=".619" stop-color="#dca51f"/><stop offset="1" stop-color="#e0a922"/></linearGradient><path fill="url(#DVu_EwlVcyi3M2~gQlPREa)" d="M32.916,18h-0.527v4h0.703c0.515,0,0.954-0.312,1.041-0.74l0.344-1.703 C34.642,18.743,33.897,18,32.916,18z"/><linearGradient id="DVu_EwlVcyi3M2~gQlPREb" x1="200.917" x2="202.251" y1="20" y2="20" gradientTransform="matrix(-1 0 0 1 216 0)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#c48f0c"/><stop offset=".251" stop-color="#d19b16"/><stop offset=".619" stop-color="#dca51f"/><stop offset="1" stop-color="#e0a922"/></linearGradient><path fill="url(#DVu_EwlVcyi3M2~gQlPREb)" d="M15.084,18h0.527v4h-0.703c-0.515,0-0.954-0.312-1.041-0.74l-0.344-1.703 C13.358,18.743,14.103,18,15.084,18z"/><radialGradient id="DVu_EwlVcyi3M2~gQlPREc" cx="-30.778" cy="-.539" r="12.224" gradientTransform="translate(51.135 19.175) scale(.8816)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ffcf54"/><stop offset=".261" stop-color="#fdcb4d"/><stop offset=".639" stop-color="#f7c13a"/><stop offset="1" stop-color="#f0b421"/></radialGradient><path fill="url(#DVu_EwlVcyi3M2~gQlPREc)" d="M24,6.4c-4.441,0-9,0.675-9,10.275c0,0.768,0,5.877,0,6.698C15,26.8,20.4,31,24,31 s9-4.2,9-7.627c0-0.821,0-5.929,0-6.698C33,7.075,28.441,6.4,24,6.4z"/><radialGradient id="DVu_EwlVcyi3M2~gQlPREd" cx="-40.48" cy="-14.192" r="28.915" gradientTransform="translate(51.135 19.175) scale(.8816)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#c26715"/><stop offset=".508" stop-color="#b85515"/><stop offset="1" stop-color="#ad3f16"/></radialGradient><path fill="url(#DVu_EwlVcyi3M2~gQlPREd)" d="M24,5.545c-4.354,0-5,1.636-5,1.636c-1.77,0.261-5,2.854-5,5.818c0,1.654,0.265,2.876,1,7 c0.545-6.545,2.249-9,4-9c1.267,0,2.273,1,5,1c2.303,0,2.875-1,5-1c3,0,4,7.968,4,9c0.601-3.01,1-5.555,1-7 C34,9.57,30.209,5.545,24,5.545z"/><radialGradient id="DVu_EwlVcyi3M2~gQlPREe" cx="-53.966" cy="-12.256" r="33.398" gradientTransform="matrix(.8431 0 0 .8816 68.067 19.175)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#c26715"/><stop offset=".508" stop-color="#b85515"/><stop offset="1" stop-color="#ad3f16"/></radialGradient><path fill="url(#DVu_EwlVcyi3M2~gQlPREe)" d="M24.219,5c-4.164,0-5.216,2.182-5.216,2.182c-0.042,1.159,0.522,2.182,0.522,2.182 S20.285,11,24.625,11C27.245,11,31,9.365,31,5C31,5,30.157,5,24.219,5z"/><rect width="2" height="5" x="23" y="39" fill="#21a366"/></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="iucqluk~4TSJfp3H4setfa" x1="30.123" x2="41.07" y1="36.377" y2="25.43" gradientUnits="userSpaceOnUse"><stop offset=".365" stop-color="#199ae0"/><stop offset=".699" stop-color="#1898de"/><stop offset=".819" stop-color="#1691d8"/><stop offset=".905" stop-color="#1186cc"/><stop offset=".974" stop-color="#0a75bc"/><stop offset="1" stop-color="#076cb3"/></linearGradient><path fill="url(#iucqluk~4TSJfp3H4setfa)" d="M45.516,25.667L33.668,37.516c-0.645,0.645-1.69,0.645-2.335,0l-2.349-2.349 c-0.645-0.645-0.645-1.69,0-2.335l11.849-11.849c0.645-0.645,1.69-0.645,2.335,0l2.349,2.349 C46.161,23.977,46.161,25.023,45.516,25.667z"/><linearGradient id="iucqluk~4TSJfp3H4setfb" x1="19.812" x2="38.111" y1="15.281" y2="33.58" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1ea2e4"/><stop offset="1" stop-color="#32bdef"/></linearGradient><path fill="url(#iucqluk~4TSJfp3H4setfb)" d="M45.516,23.332L32.667,10.484c-0.645-0.645-1.69-0.645-2.335,0l-2.349,2.349 c-0.645,0.645-0.645,1.69,0,2.335L32.816,20H15.5c-0.828,0-1.5,0.672-1.5,1.5v5c0,0.828,0.672,1.5,1.5,1.5h25.316l0,0h2.368 l2.333-2.333C46.161,25.023,46.161,23.977,45.516,23.332z"/><linearGradient id="iucqluk~4TSJfp3H4setfc" x1="145.334" x2="155.403" y1="-109.48" y2="-109.48" gradientTransform="rotate(-90 147.24 26.76)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1ea2e4"/><stop offset="1" stop-color="#32bdef"/></linearGradient><path fill="url(#iucqluk~4TSJfp3H4setfc)" d="M12,21v6c0,0.552-0.448,1-1,1h0c-0.552,0-1-0.448-1-1v-6c0-0.552,0.448-1,1-1h0 C11.552,20,12,20.448,12,21z"/><linearGradient id="iucqluk~4TSJfp3H4setfd" x1="145.334" x2="155.403" y1="-113.48" y2="-113.48" gradientTransform="rotate(-90 147.24 26.76)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1ea2e4"/><stop offset="1" stop-color="#32bdef"/></linearGradient><path fill="url(#iucqluk~4TSJfp3H4setfd)" d="M8,21v6c0,0.552-0.448,1-1,1h0c-0.552,0-1-0.448-1-1v-6c0-0.552,0.448-1,1-1h0 C7.552,20,8,20.448,8,21z"/><linearGradient id="iucqluk~4TSJfp3H4setfe" x1="145.334" x2="155.403" y1="-117.48" y2="-117.48" gradientTransform="rotate(-90 147.24 26.76)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1ea2e4"/><stop offset="1" stop-color="#32bdef"/></linearGradient><path fill="url(#iucqluk~4TSJfp3H4setfe)" d="M4,21v6c0,0.552-0.448,1-1,1h0c-0.552,0-1-0.448-1-1v-6c0-0.552,0.448-1,1-1h0 C3.552,20,4,20.448,4,21z"/></svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="OhW_8EWeW2cETtZ_QU~4ka" x1="24" x2="24" y1="6.121" y2="42.039" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#33bef0"/><stop offset="1" stop-color="#0a85d9"/></linearGradient><path fill="url(#OhW_8EWeW2cETtZ_QU~4ka)" d="M40,42H8c-1.1,0-2-0.9-2-2V8c0-1.1,0.9-2,2-2h32c1.1,0,2,0.9,2,2v32C42,41.1,41.1,42,40,42z"/><path d="M37.02,20.243V19.5c0-0.827-0.673-1.5-1.5-1.5h-2.927c-0.827,0-1.5,0.673-1.5,1.5v0.529 C30.513,18.94,29.296,18,26.809,18H23.78c-0.827,0-1.5,0.673-1.5,1.5v7.729l-2.929-8.234C19.139,18.4,18.571,18,17.939,18h-1.969 c-0.627,0-1.193,0.396-1.409,0.987l-3.336,9.164c-0.154,0.422-0.093,0.893,0.165,1.261C11.647,29.78,12.069,30,12.519,30h1.537 c0.658,0,1.233-0.421,1.43-1.047l0.373-1.177h2.094l0.378,1.183C18.53,29.581,19.104,30,19.759,30h1.557 c0.446,0,0.866-0.218,1.124-0.582c0.029-0.041,0.046-0.087,0.07-0.13C22.775,29.714,23.243,30,23.78,30h1.252 c0.827,0,1.5-0.673,1.5-1.5v-1.945h0.074c1.449,0,2.643-0.395,3.549-1.172c0.95-0.816,1.433-1.892,1.433-3.196 c0-0.281-0.021-0.597-0.078-0.925c0.122,0.13,0.259,0.246,0.42,0.326v4.823c-0.495,0.245-0.837,0.756-0.837,1.345V28.5 c0,0.827,0.673,1.5,1.5,1.5h2.927c0.827,0,1.5-0.673,1.5-1.5v-0.743c0-0.589-0.342-1.1-0.837-1.345v-4.823 C36.678,21.343,37.02,20.833,37.02,20.243z M27.217,22.271c0,0.33,0,0.553-0.685,0.574v-1.114 C27.217,21.751,27.217,21.961,27.217,22.271z" opacity=".05"/><path d="M18.881,19.164c-0.142-0.397-0.521-0.664-0.941-0.664h-1.969c-0.418,0-0.796,0.264-0.939,0.658 l-3.336,9.165c-0.098,0.269-0.059,0.568,0.104,0.803c0.164,0.234,0.433,0.374,0.719,0.374h1.537c0.438,0,0.822-0.281,0.953-0.698 l0.483-1.526h2.825l0.488,1.53c0.134,0.416,0.517,0.695,0.953,0.695h1.557c0.284,0,0.552-0.139,0.716-0.371 c0.164-0.232,0.205-0.531,0.109-0.799L18.881,19.164z M17.481,24.546h-1.188l0.596-1.856L17.481,24.546z" opacity=".07"/><path d="M26.809,18.5H23.78c-0.552,0-1,0.449-1,1v9c0,0.551,0.448,1,1,1h1.252c0.552,0,1-0.449,1-1v-2.445 h0.574c1.326,0,2.41-0.354,3.224-1.052c0.835-0.717,1.258-1.665,1.258-2.816C31.088,20.505,30.346,18.5,26.809,18.5z M27.717,22.271 c0,0.559-0.157,1.076-1.299,1.076h-0.386v-2.117h0.386C27.56,21.229,27.717,21.73,27.717,22.271z" opacity=".07"/><path d="M36.52,20.243V19.5c0-0.551-0.448-1-1-1h-2.927c-0.552,0-1,0.449-1,1v0.743 c0,0.496,0.363,0.909,0.837,0.987v5.54c-0.474,0.078-0.837,0.491-0.837,0.987V28.5c0,0.551,0.448,1,1,1h2.927c0.552,0,1-0.449,1-1 v-0.743c0-0.496-0.363-0.909-0.837-0.987v-5.54C36.156,21.152,36.52,20.739,36.52,20.243z M36.019,19.5L36.019,19.5L36.019,19.5 L36.019,19.5z" opacity=".07"/><path fill="#fff" d="M21.315,29h-1.557c-0.217,0-0.41-0.141-0.476-0.348l-0.6-1.877h-3.556l-0.594,1.875 C14.466,28.859,14.273,29,14.055,29h-1.537c-0.261,0-0.443-0.26-0.354-0.505l3.337-9.166c0.072-0.198,0.26-0.329,0.47-0.329h1.968 c0.212,0,0.4,0.133,0.471,0.332l3.26,9.165C21.757,28.743,21.575,29,21.315,29z M18.166,25.046l-1.074-3.361 c-0.079-0.251-0.135-0.551-0.167-0.9h-0.056c-0.023,0.293-0.081,0.583-0.174,0.872l-1.088,3.389H18.166z"/><path fill="#fff" d="M25.532,25.555V28.5c0,0.276-0.224,0.5-0.5,0.5H23.78c-0.276,0-0.5-0.224-0.5-0.5v-9 c0-0.276,0.224-0.5,0.5-0.5h3.029c2.52,0,3.78,1.062,3.78,3.187c0,1.004-0.361,1.817-1.084,2.437s-1.689,0.931-2.897,0.931H25.532z M25.532,20.729v3.117h0.886c1.199,0,1.799-0.525,1.799-1.576c0-1.027-0.6-1.541-1.799-1.541H25.532z"/><path fill="#fff" d="M36.019,19.5v0.743c0,0.276-0.224,0.5-0.5,0.5h-0.337v6.513h0.337c0.276,0,0.5,0.224,0.5,0.5V28.5 c0,0.276-0.224,0.5-0.5,0.5h-2.926c-0.276,0-0.5-0.224-0.5-0.5v-0.743c0-0.276,0.224-0.5,0.5-0.5h0.337v-6.513h-0.337 c-0.276,0-0.5-0.224-0.5-0.5V19.5c0-0.276,0.224-0.5,0.5-0.5h2.926C35.795,19,36.019,19.224,36.019,19.5z"/></svg>

After

Width:  |  Height:  |  Size: 3.6 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="ldLw80Wb5w9tTRcKjgX8Ga" x1="2.252" x2="34.131" y1="12.996" y2="42.423" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0077d2"/><stop offset="1" stop-color="#0b59a2"/></linearGradient><path fill="url(#ldLw80Wb5w9tTRcKjgX8Ga)" d="M23.008,22.387L6.256,31.181c-1.523,0.8-1.523,2.98,0,3.779l16.752,8.795 c0.621,0.326,1.363,0.326,1.984,0l16.752-8.795c1.523-0.8,1.523-2.98,0-3.779l-16.752-8.795 C24.371,22.06,23.629,22.06,23.008,22.387z"/><path d="M25.457,35.569L37.78,29.1l-12.787-6.713c-0.621-0.326-1.363-0.326-1.984,0L10.22,29.1l12.322,6.469 c0.447,0.235,0.952,0.36,1.458,0.36S25.011,35.805,25.457,35.569z" opacity=".05"/><path d="M25.225,35.127l12.017-6.309l-12.25-6.431c-0.621-0.326-1.363-0.326-1.984,0l-12.25,6.431 l12.017,6.309c0.376,0.198,0.8,0.303,1.225,0.303S24.849,35.325,25.225,35.127z" opacity=".07"/><linearGradient id="ldLw80Wb5w9tTRcKjgX8Gb" x1="6.773" x2="38.652" y1="8.098" y2="37.525" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#2aa4f4"/><stop offset="1" stop-color="#007ad9"/></linearGradient><path fill="url(#ldLw80Wb5w9tTRcKjgX8Gb)" d="M23.008,13.316L6.256,22.11c-1.523,0.8-1.523,2.98,0,3.779l16.752,8.795 c0.621,0.326,1.363,0.326,1.984,0l16.752-8.795c1.523-0.8,1.523-2.98,0-3.779l-16.752-8.795 C24.371,12.989,23.629,12.989,23.008,13.316z"/><path d="M25.457,26.498l12.322-6.469l-12.787-6.713c-0.621-0.326-1.363-0.326-1.984,0l-12.787,6.713 l12.321,6.469c0.447,0.235,0.952,0.36,1.458,0.36S25.011,26.733,25.457,26.498z" opacity=".05"/><path d="M25.225,26.056l12.017-6.309l-12.25-6.431c-0.621-0.326-1.363-0.326-1.984,0l-12.25,6.431 l12.017,6.309c0.376,0.198,0.8,0.303,1.225,0.303S24.849,26.254,25.225,26.056z" opacity=".07"/><linearGradient id="ldLw80Wb5w9tTRcKjgX8Gc" x1="11.294" x2="43.173" y1="3.201" y2="32.627" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#75daff"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#ldLw80Wb5w9tTRcKjgX8Gc)" d="M23.008,4.245L6.256,13.039c-1.523,0.8-1.523,2.98,0,3.779l16.752,8.795 c0.621,0.326,1.363,0.326,1.984,0l16.752-8.795c1.523-0.8,1.523-2.98,0-3.779L24.992,4.245C24.371,3.918,23.629,3.918,23.008,4.245z"/></svg>

After

Width:  |  Height:  |  Size: 2.2 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#50e6ff" d="M44,8v33h-8V8c0-0.552,0.448-1,1-1h6C43.552,7,44,7.448,44,8z"/><path fill="#35c1f1" d="M36,15v26h-8V15H36z"/><path fill="#199be2" d="M28,13v28h-8V13c0-0.552,0.448-1,1-1h6C27.552,12,28,12.448,28,13z"/><path fill="#0078d4" d="M20,20v21h-8V20H20z"/><path fill="#0d62ab" d="M12,17v24H4V17c0-0.552,0.448-1,1-1h6C11.552,16,12,16.448,12,17z"/></svg>

After

Width:  |  Height:  |  Size: 453 B

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#e3a600" d="M20.834,29.595l-6.808,13.958c-0.136,0.279-0.33,0.518-0.579,0.708l-0.91,0.702 c-0.302,0.231-0.708,0.266-1.05,0.1l-2.481-1.21c-0.342-0.167-0.564-0.509-0.568-0.889l-0.007-1.149 c-0.003-0.313,0.065-0.613,0.201-0.892l0.671-1.375c0.609,0.074,1.271-0.326,1.556-0.91s0.192-1.353-0.241-1.787l0.877-1.798 c0.609,0.074,1.271-0.326,1.556-0.91s0.192-1.353-0.241-1.787l2.63-5.393L20.834,29.595z"/><path fill="#edbe00" d="M25.59,13c-4.967-2.423-10.949-0.363-13.372,4.604s-0.363,10.949,4.604,13.372 s10.949,0.363,13.372-4.604S30.557,15.423,25.59,13z"/><path fill="#edbe00" d="M25.59,13c-0.77-0.37-1.56-0.64-2.36-0.8c-0.31-0.07-0.62-0.12-0.93-0.15 c-2.337-0.258-4.68,0.321-6.613,1.595l-1.123-0.323l-0.461,1.62c-0.75,0.756-1.39,1.646-1.883,2.657 c-2.42,4.97-0.36,10.95,4.6,13.38c1.75,0.85,3.63,1.15,5.43,0.95c0.32-0.03,0.64-0.08,0.96-0.14c2.93-0.6,5.57-2.51,6.98-5.42 C32.62,21.4,30.56,15.42,25.59,13z"/><linearGradient id="bN0gXF350I4SeGvD0XhmSa" x1="27" x2="27" y1="18.098" y2="2.759" gradientTransform="matrix(1 0 0 -1 0 50)" gradientUnits="userSpaceOnUse"><stop offset=".014" stop-color="#e5a505"/><stop offset=".063" stop-color="#e9a804"/><stop offset=".128" stop-color="#f4b102"/><stop offset=".169" stop-color="#fbb600"/><stop offset=".323" stop-color="#fdb700"/></linearGradient><path fill="url(#bN0gXF350I4SeGvD0XhmSa)" d="M30,29v15.53c0,0.31-0.07,0.61-0.21,0.89l-0.51,1.03c-0.17,0.34-0.52,0.55-0.9,0.55h-2.76 c-0.38,0-0.73-0.21-0.9-0.55l-0.51-1.03C24.07,45.14,24,44.84,24,44.53V43c0.58-0.2,1-0.85,1-1.5s-0.42-1.3-1-1.5v-2 c0.58-0.2,1-0.85,1-1.5s-0.42-1.3-1-1.5v-6H30z"/><path d="M25.59,13c-0.77-0.37-1.56-0.64-2.36-0.8c-0.31-0.07-0.62-0.12-0.93-0.15 C18.58,13.82,16,17.61,16,22c0,4.37,2.56,8.15,6.25,9.93c0.32-0.03,0.64-0.08,0.96-0.14c2.93-0.6,5.57-2.51,6.98-5.42 C32.62,21.4,30.56,15.42,25.59,13z M27,20c-1.1,0-2-0.9-2-2s0.9-2,2-2s2,0.9,2,2S28.1,20,27,20z" opacity=".05"/><path d="M25.59,13c-0.77-0.37-1.56-0.64-2.36-0.8c-3.93,1.52-6.73,5.34-6.73,9.8 c0,4.45,2.79,8.27,6.71,9.79c2.93-0.6,5.57-2.51,6.98-5.42C32.62,21.4,30.56,15.42,25.59,13z M27,20.5c-1.38,0-2.5-1.12-2.5-2.5 s1.12-2.5,2.5-2.5s2.5,1.12,2.5,2.5S28.38,20.5,27,20.5z" opacity=".07"/><linearGradient id="bN0gXF350I4SeGvD0XhmSb" x1="22.304" x2="31.696" y1="36.832" y2="19.168" gradientTransform="matrix(1 0 0 -1 0 50)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fede00"/><stop offset="1" stop-color="#ffd000"/></linearGradient><path fill="url(#bN0gXF350I4SeGvD0XhmSb)" d="M27,12c-5.526,0-10,4.474-10,10s4.474,10,10,10s10-4.474,10-10S32.526,12,27,12z M27,21 c-1.656,0-3-1.344-3-3s1.344-3,3-3s3,1.344,3,3S28.656,21,27,21z"/><linearGradient id="bN0gXF350I4SeGvD0XhmSc" x1="15.816" x2="17.996" y1="63.86" y2="55.91" gradientTransform="matrix(1 0 0 -1 0 50)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fcfcfc"/><stop offset=".495" stop-color="#f4f4f4"/><stop offset=".946" stop-color="#e8e8e8"/><stop offset="1" stop-color="#e8e8e8"/></linearGradient><path fill="url(#bN0gXF350I4SeGvD0XhmSc)" d="M22.5,1C17.26,1,13,5.26,13,10.5c0,1.61,0.385,3.13,1.095,4.45 c0.48-0.5,1.022-0.935,1.592-1.305C15.237,12.685,15,11.62,15,10.5C15,6.36,18.36,3,22.5,3S30,6.36,30,10.5 c0,0.65-0.08,1.29-0.25,1.89c-0.25,0.99-0.7,1.9-1.31,2.68c-0.06,0.07-0.11,0.14-0.17,0.21c-1.06,1.29-2.56,2.22-4.26,2.57 C24,17.9,24,17.95,24,18c0,0.66,0.22,1.28,0.58,1.77c2.03-0.45,3.8-1.55,5.11-3.07l0.01-0.01c0.88-1.01,1.55-2.22,1.92-3.55 C31.87,12.3,32,11.42,32,10.5C32,5.26,27.74,1,22.5,1z"/><path d="M24.5,18c0-0.09,0.01-0.19,0.01-0.28c-0.16,0.05-0.33,0.1-0.5,0.13 C24,17.9,24,17.95,24,18c0,0.1,0.01,0.2,0.02,0.3c0.05,0.55,0.25,1.05,0.56,1.47c0.18-0.04,0.36-0.08,0.53-0.14 C24.73,19.2,24.5,18.62,24.5,18z" opacity=".07"/><path d="M25,18c0-0.16,0.02-0.31,0.05-0.46c-0.17,0.08-0.36,0.14-0.54,0.18 c-0.16,0.05-0.33,0.1-0.5,0.13C24,17.9,24,17.95,24,18c0,0.1,0.01,0.2,0.02,0.3c0.05,0.55,0.25,1.05,0.56,1.47 c0.18-0.04,0.36-0.08,0.53-0.14c0.18-0.05,0.36-0.1,0.53-0.17C25.25,19.09,25,18.57,25,18z" opacity=".05"/></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#50e6ff" d="M6.673,11.767l24-8.308C31.322,3.235,32,3.717,32,4.404v18.884c0,0.426-0.27,0.806-0.673,0.945 l-24,8.308C6.678,32.765,6,32.283,6,31.596V12.712C6,12.286,6.27,11.906,6.673,11.767z"/><path d="M31.327,24.233C31.73,24.094,32,23.714,32,23.288V8.672l-20.653,7.149 C10.541,16.1,10,16.859,10,17.712v13.903L31.327,24.233z" opacity=".05"/><path d="M31.327,24.233C31.73,24.094,32,23.714,32,23.288V9.202l-20.49,7.093 c-0.604,0.208-1.01,0.778-1.01,1.417v13.73L31.327,24.233z" opacity=".05"/><path fill="#199be2" d="M11.673,16.767l24-8.308C36.322,8.235,37,8.717,37,9.404v18.884c0,0.426-0.27,0.806-0.673,0.945 l-24,8.308C11.678,37.765,11,37.283,11,36.596V17.712C11,17.286,11.27,16.906,11.673,16.767z"/><path d="M36.327,29.233C36.73,29.094,37,28.714,37,28.288V13.672l-20.653,7.149 C15.541,21.1,15,21.859,15,22.712v13.903L36.327,29.233z" opacity=".05"/><path d="M36.327,29.233C36.73,29.094,37,28.714,37,28.288V14.202l-20.49,7.093 c-0.604,0.208-1.01,0.778-1.01,1.417v13.73L36.327,29.233z" opacity=".05"/><path fill="#0d62ab" d="M16.673,21.767l24-8.308C41.322,13.235,42,13.717,42,14.404v18.884c0,0.426-0.27,0.806-0.673,0.945 l-24,8.308C16.678,42.765,16,42.283,16,41.596V22.712C16,22.286,16.27,21.906,16.673,21.767z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><path fill="#7b83eb" d="M24,13H3v24c0,1.105,0.895,2,2,2h19V13z"/><path fill="#5059c9" d="M45,13H24v26h19c1.105,0,2-0.895,2-2V13z"/><path fill="#3a41ac" d="M43,9H5c-1.105,0-2,0.895-2,2v2h42v-2C45,9.895,44.105,9,43,9z"/><circle cx="7" cy="18" r="1" fill="#3a41ac"/><circle cx="7" cy="22" r="1" fill="#3a41ac"/><circle cx="7" cy="26" r="1" fill="#3a41ac"/><circle cx="7" cy="34" r="1" fill="#3a41ac"/><path fill="#3a41ac" d="M20,19h-7c-0.552,0-1-0.448-1-1v0c0-0.552,0.448-1,1-1h7c0.552,0,1,0.448,1,1v0 C21,18.552,20.552,19,20,19z"/><path fill="#3a41ac" d="M20,27h-7c-0.552,0-1-0.448-1-1v0c0-0.552,0.448-1,1-1h7c0.552,0,1,0.448,1,1v0 C21,26.552,20.552,27,20,27z"/><path fill="#3a41ac" d="M20,23h-7c-0.552,0-1-0.448-1-1v0c0-0.552,0.448-1,1-1h7c0.552,0,1,0.448,1,1v0 C21,22.552,20.552,23,20,23z"/><path fill="#3a41ac" d="M20,35h-7c-0.552,0-1-0.448-1-1l0,0c0-0.552,0.448-1,1-1h7c0.552,0,1,0.448,1,1l0,0 C21,34.552,20.552,35,20,35z"/><path fill="#3a41ac" d="M20,31h-2c-0.552,0-1-0.448-1-1v0c0-0.552,0.448-1,1-1h2c0.552,0,1,0.448,1,1v0 C21,30.552,20.552,31,20,31z"/><circle cx="28" cy="18" r="1" fill="#7b83eb"/><circle cx="28" cy="22" r="1" fill="#7b83eb"/><circle cx="28" cy="26" r="1" fill="#7b83eb"/><circle cx="28" cy="34" r="1" fill="#7b83eb"/><path fill="#7b83eb" d="M41,19h-7c-0.552,0-1-0.448-1-1v0c0-0.552,0.448-1,1-1h7c0.552,0,1,0.448,1,1v0 C42,18.552,41.552,19,41,19z"/><path fill="#7b83eb" d="M41,27h-7c-0.552,0-1-0.448-1-1v0c0-0.552,0.448-1,1-1h7c0.552,0,1,0.448,1,1v0 C42,26.552,41.552,27,41,27z"/><path fill="#7b83eb" d="M41,23h-7c-0.552,0-1-0.448-1-1v0c0-0.552,0.448-1,1-1h7c0.552,0,1,0.448,1,1v0 C42,22.552,41.552,23,41,23z"/><path fill="#7b83eb" d="M41,35h-7c-0.552,0-1-0.448-1-1l0,0c0-0.552,0.448-1,1-1h7c0.552,0,1,0.448,1,1l0,0 C42,34.552,41.552,35,41,35z"/><path fill="#7b83eb" d="M41,31h-2c-0.552,0-1-0.448-1-1v0c0-0.552,0.448-1,1-1h2c0.552,0,1,0.448,1,1v0 C42,30.552,41.552,31,41,31z"/></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="Nc0B9TSPkzHlq4dNHwoWda" x1="7.793" x2="23.566" y1="8.207" y2="23.98" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#7dd8f3"/><stop offset="1" stop-color="#45b0d0"/></linearGradient><path fill="url(#Nc0B9TSPkzHlq4dNHwoWda)" d="M5,10h26v18H7c-1.105,0-2-0.895-2-2V10z"/><linearGradient id="Nc0B9TSPkzHlq4dNHwoWdb" x1="5" x2="31" y1="8" y2="8" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#0077d2"/><stop offset="1" stop-color="#0b59a2"/></linearGradient><path fill="url(#Nc0B9TSPkzHlq4dNHwoWdb)" d="M31,10V8c0-1.105-0.895-2-2-2H7C5.895,6,5,6.895,5,8v2H31z"/><linearGradient id="Nc0B9TSPkzHlq4dNHwoWdc" x1="12.5" x2="12.5" y1="15" y2="24.438" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#199ae0"/><stop offset="1" stop-color="#0782d8"/></linearGradient><path fill="url(#Nc0B9TSPkzHlq4dNHwoWdc)" d="M15,23h-5c-0.552,0-1-0.448-1-1v-6c0-0.552,0.448-1,1-1h5c0.552,0,1,0.448,1,1v6 C16,22.552,15.552,23,15,23z"/><linearGradient id="Nc0B9TSPkzHlq4dNHwoWdd" x1="23.5" x2="23.5" y1="15" y2="24.438" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#199ae0"/><stop offset="1" stop-color="#0782d8"/></linearGradient><path fill="url(#Nc0B9TSPkzHlq4dNHwoWdd)" d="M26,23h-5c-0.552,0-1-0.448-1-1v-6c0-0.552,0.448-1,1-1h5c0.552,0,1,0.448,1,1v6 C27,22.552,26.552,23,26,23z"/><path d="M31,28v-9H19c-1.654,0-3,1.346-3,3v6H31z" opacity=".05"/><path d="M31,28v-8.5H19c-1.378,0-2.5,1.122-2.5,2.5v6H31z" opacity=".07"/><linearGradient id="Nc0B9TSPkzHlq4dNHwoWde" x1="17" x2="43" y1="33" y2="33" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#9dffce"/><stop offset="1" stop-color="#50d18d"/></linearGradient><path fill="url(#Nc0B9TSPkzHlq4dNHwoWde)" d="M17,24h26v16c0,1.105-0.895,2-2,2H19c-1.105,0-2-0.895-2-2V24z"/><linearGradient id="Nc0B9TSPkzHlq4dNHwoWdf" x1="17" x2="43" y1="22" y2="22" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#21ad64"/><stop offset="1" stop-color="#088242"/></linearGradient><path fill="url(#Nc0B9TSPkzHlq4dNHwoWdf)" d="M43,24v-2c0-1.105-0.895-2-2-2H19c-1.105,0-2,0.895-2,2v2H43z"/><g><linearGradient id="Nc0B9TSPkzHlq4dNHwoWdg" x1="21" x2="28" y1="33" y2="33" gradientUnits="userSpaceOnUse"><stop offset=".824" stop-color="#135d36"/><stop offset=".931" stop-color="#125933"/><stop offset="1" stop-color="#11522f"/></linearGradient><path fill="url(#Nc0B9TSPkzHlq4dNHwoWdg)" d="M27,37h-5c-0.552,0-1-0.448-1-1v-6c0-0.552,0.448-1,1-1h5c0.552,0,1,0.448,1,1v6 C28,36.552,27.552,37,27,37z"/><linearGradient id="Nc0B9TSPkzHlq4dNHwoWdh" x1="32" x2="39" y1="33" y2="33" gradientUnits="userSpaceOnUse"><stop offset=".824" stop-color="#135d36"/><stop offset=".931" stop-color="#125933"/><stop offset="1" stop-color="#11522f"/></linearGradient><path fill="url(#Nc0B9TSPkzHlq4dNHwoWdh)" d="M38,37h-5c-0.552,0-1-0.448-1-1v-6c0-0.552,0.448-1,1-1h5c0.552,0,1,0.448,1,1v6 C39,36.552,38.552,37,38,37z"/></g></svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="Y2MBg9lti7D0ov~adiJgUa" x1="16.758" x2="30.883" y1="3.118" y2="17.242" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f44f5a"/><stop offset=".443" stop-color="#ee3d4a"/><stop offset="1" stop-color="#e52030"/></linearGradient><path fill="url(#Y2MBg9lti7D0ov~adiJgUa)" d="M24,4l-1,1v19h2.414L38.14,11.274V9.86C34.52,6.24,29.52,4,24,4z"/><linearGradient id="Y2MBg9lti7D0ov~adiJgUb" x1="-33.907" x2="-.208" y1="19.952" y2="53.652" gradientTransform="rotate(45.001 24.001 92)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#fed100"/><stop offset="1" stop-color="#e36001"/></linearGradient><path fill="url(#Y2MBg9lti7D0ov~adiJgUb)" d="M38.142,9.858L24,24l1,1l17.997,0l1-1C43.997,18.881,42.045,13.761,38.142,9.858z"/><linearGradient id="Y2MBg9lti7D0ov~adiJgUc" x1="-46.93" x2="-36.223" y1="74.93" y2="85.637" gradientTransform="rotate(90 24 92)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#ffd747"/><stop offset=".482" stop-color="#ffd645"/><stop offset="1" stop-color="#f5bc00"/></linearGradient><path fill="url(#Y2MBg9lti7D0ov~adiJgUc)" d="M44,24H24v1.414L36.726,38.14h1.414C41.76,34.52,44,29.52,44,24z"/><linearGradient id="Y2MBg9lti7D0ov~adiJgUd" x1="-27.013" x2="-16.306" y1="123.013" y2="133.72" gradientTransform="rotate(134.999 24 92)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#33c481"/><stop offset="1" stop-color="#21a366"/></linearGradient><path fill="url(#Y2MBg9lti7D0ov~adiJgUd)" d="M38.142,38.142L24,24l-1,1l0,17.997l1,1C29.119,43.997,34.239,42.045,38.142,38.142z"/><linearGradient id="Y2MBg9lti7D0ov~adiJgUe" x1="21.07" x2="31.777" y1="142.93" y2="153.637" gradientTransform="rotate(180 24 92)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#28afea"/><stop offset="1" stop-color="#0b88da"/></linearGradient><path fill="url(#Y2MBg9lti7D0ov~adiJgUe)" d="M24,44V24h-1.414L9.86,36.726v1.414C13.48,41.76,18.48,44,24,44z"/><linearGradient id="Y2MBg9lti7D0ov~adiJgUf" x1="67.621" x2="78.328" y1="121.481" y2="132.188" gradientTransform="rotate(-134.999 24 92)" gradientUnits="userSpaceOnUse"><stop offset=".002" stop-color="#427fdb"/><stop offset=".397" stop-color="#2668cb"/><stop offset=".763" stop-color="#1358bf"/><stop offset="1" stop-color="#0c52bb"/></linearGradient><path fill="url(#Y2MBg9lti7D0ov~adiJgUf)" d="M9.858,38.142L24,24l-1-1L5.003,23l-1,1C4.003,29.119,5.955,34.239,9.858,38.142z"/><linearGradient id="Y2MBg9lti7D0ov~adiJgUg" x1="89.07" x2="99.777" y1="74.93" y2="85.637" gradientTransform="rotate(-90 24 92)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#a235d4"/><stop offset=".441" stop-color="#a033d1"/><stop offset=".702" stop-color="#982cc9"/><stop offset=".915" stop-color="#8b21bb"/><stop offset="1" stop-color="#831bb3"/></linearGradient><path fill="url(#Y2MBg9lti7D0ov~adiJgUg)" d="M4,24h20v-1.414L11.274,9.86H9.86C6.24,13.48,4,18.48,4,24z"/><linearGradient id="Y2MBg9lti7D0ov~adiJgUh" x1="69.153" x2="96.498" y1="26.847" y2="54.191" gradientTransform="rotate(-45.001 23.999 92)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#e83c67"/><stop offset=".423" stop-color="#c5214a"/><stop offset=".773" stop-color="#b01038"/><stop offset="1" stop-color="#a80a31"/></linearGradient><path fill="url(#Y2MBg9lti7D0ov~adiJgUh)" d="M9.858,9.858L24,24V4.003C18.881,4.003,13.761,5.955,9.858,9.858z"/></svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="CrkWqRsuEmwpViPxws7A9a" x1="12.686" x2="35.58" y1="4.592" y2="41.841" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#737be6"/><stop offset="1" stop-color="#4750b3"/></linearGradient><path fill="url(#CrkWqRsuEmwpViPxws7A9a)" d="M42,8H6c-1.105,0-2,0.895-2,2v26c0,1.105,0.895,2,2,2h8v7.998 c0,0.891,1.077,1.337,1.707,0.707L24.412,38H42c1.105,0,2-0.895,2-2V10C44,8.895,43.105,8,42,8z"/><path d="M12,22h24c1.105,0,2-0.895,2-2v-2c0-1.105-0.895-2-2-2H12c-1.105,0-2,0.895-2,2v2 C10,21.105,10.895,22,12,22z" opacity=".05"/><path d="M12,21.5h24c0.828,0,1.5-0.672,1.5-1.5v-2c0-0.828-0.672-1.5-1.5-1.5H12c-0.828,0-1.5,0.672-1.5,1.5 v2C10.5,20.828,11.172,21.5,12,21.5z" opacity=".07"/><path d="M12,30h18c1.105,0,2-0.895,2-2v-2c0-1.105-0.895-2-2-2H12c-1.105,0-2,0.895-2,2v2 C10,29.105,10.895,30,12,30z" opacity=".05"/><path d="M12,29.5h18c0.828,0,1.5-0.672,1.5-1.5v-2c0-0.828-0.672-1.5-1.5-1.5H12c-0.828,0-1.5,0.672-1.5,1.5 v2C10.5,28.828,11.172,29.5,12,29.5z" opacity=".07"/><path fill="#fff" d="M31,26v2c0,0.552-0.448,1-1,1H12c-0.552,0-1-0.448-1-1v-2c0-0.552,0.448-1,1-1h18 C30.552,25,31,25.448,31,26z"/><path fill="#fff" d="M37,18v2c0,0.552-0.448,1-1,1H12c-0.552,0-1-0.448-1-1v-2c0-0.552,0.448-1,1-1h24 C36.552,17,37,17.448,37,18z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><linearGradient id="LkaBH78Qy0LlLxZFYVKUda" x1="8" x2="40" y1="35.5" y2="35.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#11408a"/><stop offset="1" stop-color="#103f8f"/></linearGradient><path fill="url(#LkaBH78Qy0LlLxZFYVKUda)" d="M40,28H8c0,0,0,10.271,0,11c0,2.209,7.163,4,16,4s16-1.791,16-4C40,38.271,40,28,40,28z"/><linearGradient id="LkaBH78Qy0LlLxZFYVKUdb" x1="8" x2="40" y1="25.5" y2="25.5" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#1d59b3"/><stop offset="1" stop-color="#195bbc"/></linearGradient><path fill="url(#LkaBH78Qy0LlLxZFYVKUdb)" d="M40,18H8c0,0,0,10.271,0,11c0,2.209,7.163,4,16,4s16-1.791,16-4C40,28.271,40,18,40,18z"/><linearGradient id="LkaBH78Qy0LlLxZFYVKUdc" x1="8" x2="40" y1="15" y2="15" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#3079d6"/><stop offset="1" stop-color="#297cd2"/></linearGradient><path fill="url(#LkaBH78Qy0LlLxZFYVKUdc)" d="M40,8H8c0,0,0,9.756,0,10.5c0,1.933,7.163,3.5,16,3.5s16-1.567,16-3.5C40,17.756,40,8,40,8z"/><linearGradient id="LkaBH78Qy0LlLxZFYVKUdd" x1="8" x2="40" y1="8" y2="8" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#42a3f2"/><stop offset="1" stop-color="#42a4eb"/></linearGradient><ellipse cx="24" cy="8" fill="url(#LkaBH78Qy0LlLxZFYVKUdd)" rx="16" ry="3"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="96px" height="96px"><linearGradient id="WxWrlTsssL_WlnpfbClFCa" x1="24" x2="24" y1="592.908" y2="650.553" gradientTransform="matrix(1 0 0 -1 0 662)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f44f5a"/><stop offset=".443" stop-color="#ee3d4a"/><stop offset="1" stop-color="#e52030"/></linearGradient><path fill="url(#WxWrlTsssL_WlnpfbClFCa)" d="M39,10v31c0,1.105-0.895,2-2,2H11c-1.105,0-2-0.895-2-2V10H39z"/><linearGradient id="WxWrlTsssL_WlnpfbClFCb" x1="24" x2="24" y1="657.947" y2="648.199" gradientTransform="matrix(1 0 0 -1 0 662)" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#f44f5a"/><stop offset=".443" stop-color="#ee3d4a"/><stop offset="1" stop-color="#e52030"/></linearGradient><path fill="url(#WxWrlTsssL_WlnpfbClFCb)" d="M28,4h-8c-1.105,0-2,0.895-2,2v2h12V6C30,4.895,29.105,4,28,4z"/><path fill="#ffa8a8" d="M8,11v-1c0-1.657,1.343-3,3-3h26c1.657,0,3,1.343,3,3v1H8z"/><path d="M32.395,30.153L28.243,26l4.191-4.191c0.781-0.781,0.781-2.047,0-2.828 l-1.414-1.414c-0.781-0.781-2.047-0.781-2.828,0L24,21.757l-4.191-4.191c-0.781-0.781-2.047-0.781-2.828,0l-1.414,1.414 c-0.781,0.781-0.781,2.047,0,2.828l4.191,4.191l-4.192,4.192c-0.781,0.781-0.781,2.047,0,2.828l1.414,1.414 c0.781,0.781,2.047,0.781,2.828,0L24,30.241l4.153,4.153c0.781,0.781,2.047,0.781,2.828,0l1.414-1.414 C33.176,32.2,33.176,30.934,32.395,30.153z" opacity=".05"/><path d="M32.042,30.506L27.536,26l4.544-4.544c0.586-0.586,0.586-1.536,0-2.121 l-1.414-1.414c-0.586-0.586-1.536-0.586-2.121,0L24,22.464l-4.544-4.544c-0.586-0.586-1.536-0.586-2.121,0l-1.414,1.414 c-0.586,0.586-0.586,1.536,0,2.121L20.464,26l-4.545,4.545c-0.586,0.586-0.586,1.536,0,2.121l1.414,1.414 c0.586,0.586,1.536,0.586,2.121,0L24,29.536l4.506,4.506c0.586,0.586,1.536,0.586,2.121,0l1.414-1.414 C32.628,32.042,32.628,31.092,32.042,30.506z" opacity=".07"/><path fill="#fff" d="M26.828,26l4.898-4.898c0.391-0.39,0.391-1.023,0-1.414l-1.414-1.414 c-0.39-0.391-1.024-0.391-1.414,0L24,23.172l-4.898-4.898c-0.39-0.391-1.023-0.391-1.414,0l-1.414,1.414 c-0.391,0.39-0.391,1.023,0,1.414L21.172,26l-4.899,4.899c-0.391,0.39-0.391,1.023,0,1.414l1.414,1.414 c0.39,0.391,1.023,0.391,1.414,0L24,28.828l4.86,4.86c0.39,0.391,1.023,0.391,1.414,0l1.414-1.414c0.391-0.39,0.391-1.024,0-1.414 L26.828,26z"/></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48" width="48px" height="48px"><linearGradient id="YGGk5xCyNietrYbhNX6WKa" x1="13.25" x2="13.25" y1="42.071" y2="28.237" gradientTransform="rotate(-90 24 24)" gradientUnits="userSpaceOnUse"><stop offset=".365" stop-color="#199ae0"/><stop offset=".699" stop-color="#1898de"/><stop offset=".819" stop-color="#1691d8"/><stop offset=".905" stop-color="#1186cc"/><stop offset=".974" stop-color="#0a75bc"/><stop offset="1" stop-color="#076cb3"/></linearGradient><path fill="url(#YGGk5xCyNietrYbhNX6WKa)" d="M25.168,45.516l15.849-15.849c0.645-0.645,0.645-1.69,0-2.335l-3.349-3.349 c-0.645-0.645-1.69-0.645-2.335,0L19.484,39.832c-0.645,0.645-0.645,1.69,0,2.335l3.349,3.349 C23.477,46.161,24.523,46.161,25.168,45.516z"/><linearGradient id="YGGk5xCyNietrYbhNX6WKb" x1="11.984" x2="30.101" y1="2.622" y2="45.327" gradientUnits="userSpaceOnUse"><stop offset="0" stop-color="#32bdef"/><stop offset="1" stop-color="#1ea2e4"/></linearGradient><path fill="url(#YGGk5xCyNietrYbhNX6WKb)" d="M27,2h-6c-1.105,0-2,0.895-2,2v26.316l-6.333-6.333c-0.645-0.645-1.69-0.645-2.335,0 l-3.349,3.349c-0.645,0.645-0.645,1.69,0,2.335l15.849,15.849c0.645,0.645,1.69,0.645,2.335,0l3.349-3.349 C28.839,41.845,29,41.423,29,41V4C29,2.895,28.105,2,27,2z"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save