This commit is contained in:
InsanusMokrassar 2022-03-02 14:36:17 +06:00
parent f3b37cc75a
commit 91927d3b50
35 changed files with 1223 additions and 43 deletions

6
.gitignore vendored
View File

@ -10,8 +10,12 @@ build/
out/
secret.gradle
local.properties
local.*
local/
kotlin-js-store
publishing.sh
server/src/resources/web/
features/common/common/src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/Version.kt

View File

@ -27,9 +27,9 @@ kotlin {
api project(":postssystem.services.posts.client")
api "dev.inmo:micro_utils.fsm.common:$microutils_version"
api "dev.inmo:micro_utils.fsm.repos.common:$microutils_version"
api "dev.inmo:micro_utils.crypto:$microutils_version"
api libs.microutils.fsm.common
api libs.microutils.fsm.repos.common
api libs.microutils.crypto
implementation compose.runtime
}

View File

@ -43,7 +43,7 @@ class AuthView(
UIKitFlex.Alignment.Horizontal.Center
) {
Card(
Attrs(UIKitText.Alignment.Center),
Attrs(UIKitText.Alignment.Horizontal.Center),
bodyAttrs = Attrs(UIKitWidth.Fixed.Medium),
) {
CardTitle { Text("Log in") }

View File

@ -1,8 +1,8 @@
artifacts
!build
dist/
node_modules
tests/
custom
tests
themes.json
.DS_Store
/.idea
/.vscode
.idea

View File

@ -0,0 +1,24 @@
{
"root": true,
"env": {
"es2021": true,
"node": true,
"browser": false,
"commonjs": false
},
"extends": [
"../.eslintrc.json"
],
"parserOptions": {
"ecmaVersion": 2022
},
"overrides": [
{
"files": ["wrapper/*.js"],
"env": {
"browser": true,
"node": false
}
}
]
}

View File

@ -0,0 +1,91 @@
import camelize from 'camelcase';
import {basename, resolve} from 'path';
import {args, compile, glob, icons} from './util.js';
const bundles = getBundleTasks();
const components = await getComponentTasks();
const buildAll = args.all || !Object.keys(args).filter(name =>
!['d', 'debug', 'nominify', 'watch', '_'].includes(name)
).length;
if (args.h || args.help) {
console.log(`
usage:
build.js [componentA, componentB, ...] [-d|debug|nominify|watch]
examples:
build.js // builds all of uikit, including icons, components and does minification (implies 'all')
build.js uikit icons -d // builds uikit and the icons, skipping the minification and components
build.js core lightbox -d // builds uikit-core and the lightbox, skipping the minification
available components:
bundles: ${Object.keys(bundles).join(', ')}
components: ${Object.keys(components).join(', ')}
`);
process.exit(0);
}
let tasks;
const allTasks = {...bundles, ...components};
if (buildAll) {
tasks = allTasks;
} else if (args.components) {
tasks = components;
} else {
tasks = Object.keys(args)
.map(step => allTasks[step])
.filter(t => t);
}
await Promise.all(Object.values(tasks).map(task => task()));
function getBundleTasks() {
return {
core: () => compile('src/js/uikit-core.js', 'dist/js/uikit-core'),
uikit: () => compile('src/js/uikit.js', 'dist/js/uikit'),
icons: async () => compile('build/wrapper/icons.js', 'dist/js/uikit-icons', {
name: 'icons',
replaces: {ICONS: await icons('{src/images,custom}/icons/*.svg')}
}),
tests: async () => compile('tests/js/index.js', 'tests/js/test', {
name: 'test',
replaces: {TESTS: await getTestFiles()}
})
};
}
async function getComponentTasks() {
const components = await glob('src/js/components/!(index).js');
return components.reduce((components, file) => {
const name = basename(file, '.js');
components[name] = () =>
compile('build/wrapper/component.js', `dist/js/components/${name}`, {
name,
external: ['uikit', 'uikit-util'],
globals: {uikit: 'UIkit', 'uikit-util': 'UIkit.util'},
aliases: {component: resolve('src/js/components', name)},
replaces: {NAME: `'${camelize(name)}'`}
});
return components;
}, {});
}
async function getTestFiles() {
const files = await glob('tests/!(index).html', {nosort: true});
return JSON.stringify(files.map(file => basename(file, '.html')));
}

View File

@ -0,0 +1,38 @@
import {args, compile, glob, icons} from './util.js';
if (args.h || args.help) {
console.log(`
Builds additional custom uikit icons found in './custom/*/icons'
usage:
icons.js [custom|name]
-c|--custom
Specify custom folder to look for icons (default: './custom/*/icons')
-n|--name
Specify name regex to match against folder (default: '([a-z]+)/icons$')
`);
process.exit(0);
}
const path = args.c || args.custom || 'custom/*/icons';
const match = args.n || args.name || '([a-z]+)/icons$';
await Promise.all((await glob(path)).map(compileIcons));
async function compileIcons(folder) {
const [, name] = folder.toString().match(new RegExp(match, 'i'));
return compile(
'build/wrapper/icons.js',
`dist/js/uikit-icons-${name}`,
{
name,
replaces: {
ICONS: await icons(`{src/images/icons,${folder}}/*.svg`)
}
}
);
}

View File

@ -0,0 +1,91 @@
import rtlcss from 'rtlcss';
import {basename} from 'path';
import {args, banner, glob, minify, pathExists, read, readJson, renderLess, write} from './util.js';
const {rtl} = args;
const develop = args.develop || args.debug || args.d || args.nominify;
const sources = [
{src: 'src/less/uikit.less', dist: `dist/css/uikit-core${rtl ? '-rtl' : ''}.css`},
{src: 'src/less/uikit.theme.less', dist: `dist/css/uikit${rtl ? '-rtl' : ''}.css`}
];
const themes = await pathExists('themes.json') ? await readJson('themes.json') : {};
for (const src of await glob('custom/*.less')) {
const theme = basename(src, '.less');
const dist = `dist/css/uikit.${theme}${rtl ? '-rtl' : ''}.css`;
themes[theme] = {css: `../${dist}`};
if (await pathExists(`dist/js/uikit-icons-${theme}.js`)) {
themes[theme].icons = `../dist/js/uikit-icons-${theme}.js`;
}
sources.push({src, dist});
}
await Promise.all(sources.map(({src, dist}) => compile(src, dist, develop, rtl)));
if (!rtl && (Object.keys(themes).length || !await pathExists('themes.json'))) {
await write('themes.json', JSON.stringify(themes));
}
async function compile(file, dist, develop, rtl) {
const less = await read(file);
let output = (await renderLess(less, {
relativeUrls: true,
rootpath: '../../',
paths: ['src/less/', 'custom/']
})).replace(/\.\.\/dist\//g, '');
if (rtl) {
output = rtlcss.process(
output,
{
stringMap: [{
name: 'previous-next',
priority: 100,
search: ['previous', 'Previous', 'PREVIOUS'],
replace: ['next', 'Next', 'NEXT'],
options: {
scope: '*',
ignoreCase: false
}
}]
},
[
{
name: 'customNegate',
priority: 50,
directives: {
control: {},
value: []
},
processors: [
{
expr: ['--uk-position-translate-x', 'stroke-dashoffset'].join('|'),
action(prop, value, context) {
return {prop, value: context.util.negate(value)};
}
}
]
}
],
{
pre(root, postcss) {
root.prepend(postcss.comment({text: 'rtl:begin:rename'}));
root.append(postcss.comment({text: 'rtl:end:rename'}));
}
}
);
}
await write(dist, banner + output);
if (!develop) {
await minify(dist);
}
}

View File

@ -0,0 +1,4 @@
{
"private": true,
"type": "module"
}

View File

@ -0,0 +1,70 @@
import inquirer from 'inquirer';
import {args, glob, read, replaceInFile, validClassName} from './util.js';
if (args.h || args.help) {
console.log(`
usage:
prefix.js [-p{refix}=your_great_new_prefix]
example:
prefix.js // will prompt for a prefix to replace the current one with
prefix.js -p=xyz // will replace any existing prefix with xyz
`);
process.exit(0);
}
const currentPrefix = await findExistingPrefix();
const prefix = await getPrefix();
if (currentPrefix === prefix) {
throw new Error(`already prefixed with: ${prefix}`);
}
await replacePrefix(currentPrefix, prefix);
async function findExistingPrefix() {
const data = await read('dist/css/uikit.css');
const res = data.match(new RegExp(`(${validClassName.source})(-[a-z]+)?-grid`));
return res && res[1];
}
async function getPrefix() {
const prefixFromInput = args.p || args.prefix;
if (!prefixFromInput) {
const prompt = inquirer.createPromptModule();
return (await prompt({
name: 'prefix',
message: 'enter a prefix',
validate: (val, res) => val.length && val.match(validClassName) ? !!(res.prefix = val) : 'invalid prefix'
})).prefix;
}
if (validClassName.test(prefixFromInput)) {
return prefixFromInput;
} else {
throw `illegal prefix: ${prefixFromInput}`;
}
}
async function replacePrefix(from, to) {
for (const file of await glob('dist/**/*.css')) {
await replaceInFile(file, data => data.replace(
new RegExp(`${from}-${/([a-z\d-]+)/.source}`, 'g'),
`${to}-$1`
));
}
for (const file of await glob('dist/**/*.js')) {
await replaceInFile(file, data => data
.replace(new RegExp(`${from}-`, 'g'), `${to}-`)
.replace(new RegExp(`(${from})?UIkit`, 'g'), `${to === 'uk' ? '' : to}UIkit`)
);
}
}

View File

@ -0,0 +1,36 @@
import semver from 'semver';
import {args, getVersion, run} from './util.js';
const {inc} = semver;
// default exec options
if (args.f || args.force || await isDevCommit()) {
// increase version patch number
const version = inc(await getVersion(), 'patch');
// get current git hash
const hash = (await run('git rev-parse --short HEAD')).trim();
// set version of package.json
await run(`npm version ${version}-dev.${hash} --git-tag-version false`);
// create dist files
await run('yarn compile && yarn compile-rtl && yarn build-scss');
// publish to dev tag
await run('npm publish --tag dev');
}
async function isDevCommit() {
// check for changes to publish (%B: raw body (unwrapped subject and body)
const message = await run('git log -1 --pretty=%B');
// https://www.conventionalcommits.org/en/v1.0.0/
const type = message.match(/^(revert: )?(feat|fix|polish|docs|style|refactor|perf|test|workflow|ci|chore|types|build)(\(.+\))?: .{1,50}/);
return type && ['feat', 'fix', 'refactor', 'perf'].includes(type[2]);
}

View File

@ -0,0 +1,62 @@
import archiver from 'archiver';
import inquirer from 'inquirer';
import dateFormat from 'dateformat/lib/dateformat.js';
import {createWriteStream} from 'fs';
import semver from 'semver';
import {args, getVersion, glob, logFile, replaceInFile, run} from './util.js';
const {coerce, inc, prerelease, valid} = semver;
const prevVersion = await getVersion();
const version = await inquireVersion(args.v || args.version);
await Promise.all([
run(`npm version ${version} --git-tag-version false`),
replaceInFile('CHANGELOG.md', data =>
data.replace(/^##\s*WIP/m, `## ${versionFormat(version)} (${dateFormat(Date.now(), 'mmmm d, yyyy')})`)
),
replaceInFile('.github/ISSUE_TEMPLATE/bug-report.md', data =>
data.replace(prevVersion, version)
)
]);
await run('yarn compile');
await run('yarn compile-rtl');
await run('yarn build-scss');
await createPackage(version);
async function inquireVersion(v) {
if (valid(v)) {
return v;
}
const prompt = inquirer.createPromptModule();
return (await prompt({
name: 'version',
message: 'Enter a version',
default: () => inc(prevVersion, prerelease(prevVersion) ? 'prerelease' : 'patch'),
validate: val => !!val.length || 'Invalid version'
})).version;
}
async function createPackage(version) {
const file = `dist/uikit-${version}.zip`;
const archive = archiver('zip');
archive.pipe(createWriteStream(file));
(await glob('dist/{js,css}/uikit?(-icons|-rtl)?(.min).{js,css}')).forEach(file =>
archive.file(file, {name: file.substring(5)})
);
await archive.finalize();
await logFile(file);
}
function versionFormat(version) {
return [coerce(version).version].concat(prerelease(version) || []).join(' ');
}

View File

@ -0,0 +1,91 @@
import {args, glob, minify, read, renderLess, replaceInFile, validClassName} from './util.js';
if (args.h || args.help) {
console.log(`
usage:
scope.js [-s{cope}=your_great_new_scope_name][cleanup]
example:
scope.js // will scope with uk-scope
scope.js -s "my-scope" // will replace any existing scope with my-scope
scope.js cleanup // will remove current scope
`);
process.exit(0);
}
const currentScopeRe = /\/\* scoped: ([^*]*) \*\/\n/;
const currentScopeLegacyRe = /\.(uk-scope)/;
const files = await glob('dist/**/!(*.min).css');
const prevScope = await getScope(files);
if (args.cleanup && prevScope) {
await cleanup(files, prevScope);
} else if (prevScope) {
const newScope = getNewScope();
if (prevScope === newScope) {
console.warn(`Already scoped with: ${prevScope}`);
process.exit(0);
}
await cleanup(files, prevScope);
await scope(files, newScope);
} else {
await scope(files, getNewScope());
}
async function getScope(files) {
for (const file of files) {
const data = await read(file);
const [, scope] = (data.match(currentScopeRe) || data.match(currentScopeLegacyRe) || []);
if (scope) {
return scope;
}
}
return '';
}
function getNewScope() {
const scopeFromInput = args.scope || args.s || 'uk-scope';
if (validClassName.test(scopeFromInput)) {
return scopeFromInput;
} else {
throw `Illegal scope-name: ${scopeFromInput}`;
}
}
async function scope(files, scope) {
for (const file of files) {
await replaceInFile(file, async data => {
const output = await renderLess(`.${scope} {\n${stripComments(data)}\n}`);
return `/* scoped: ${scope} */\n${
output.replace(new RegExp(`.${scope}\\s((\\.(uk-(drag|modal-page|offcanvas-page|offcanvas-flip)))|html|:root)`, 'g'), '$1') // unescape
}`;
});
await minify(file);
}
}
async function cleanup(files, scope) {
const string = scope.split(' ').map(scope => `.${scope}`).join(' ');
for (const file of files) {
await replaceInFile(file, data => data
.replace(currentScopeRe, '') // remove scope comment
.replace(new RegExp(` *${string} ({[\\s\\S]*?})?`, 'g'), '') // replace classes
);
}
}
function stripComments(input) {
return input
.replace(/\/\*(.|\n)*?\*\//gm, '')
.split('\n')
.filter(line => line.trim().substr(0, 2) !== '//')
.join('\n');
}

291
client/uikit/build/scss.js Normal file
View File

@ -0,0 +1,291 @@
import NP from 'number-precision';
import {glob, read, write} from './util.js';
NP.enableBoundaryChecking(false);
const themeMixins = {};
const coreMixins = {};
const themeVar = {};
const coreVar = {};
/* template for the new components/mixins.scss file*/
const mixinTemplate = `//
// Component: Mixin
// Description: Defines mixins which are used across all components
//
// ========================================================================
// SVG
// ========================================================================
/// Replace \`$search\` with \`$replace\` in \`$string\`
/// @author Hugo Giraudel
/// @param {String} $string - Initial string
/// @param {String} $search - Substring to replace
/// @param {String} $replace ('') - New value
/// @return {String} - Updated string
@function str-replace($string, $search, $replace: '') {
$index: str-index($string, $search);
@if $index {
@return str-slice($string, 1, $index - 1) + $replace + str-replace(str-slice($string, $index + str-length($search)), $search, $replace);
}
@return $string;
}
@mixin svg-fill($src, $color-default, $color-new){
$replace-src: str-replace($src, $color-default, $color-new) !default;
$replace-src: str-replace($replace-src, "#", "%23");
background-image: url(quote($replace-src));
}`;
/* template for the inverse components */
const inverseTemplate = ` @include hook-inverse-component-base();
@include hook-inverse-component-link();
@include hook-inverse-component-heading();
@include hook-inverse-component-divider();
@include hook-inverse-component-list();
@include hook-inverse-component-icon();
@include hook-inverse-component-form();
@include hook-inverse-component-button();
@include hook-inverse-component-grid();
@include hook-inverse-component-close();
@include hook-inverse-component-totop();
@include hook-inverse-component-badge();
@include hook-inverse-component-label();
@include hook-inverse-component-article();
@include hook-inverse-component-search();
@include hook-inverse-component-nav();
@include hook-inverse-component-navbar();
@include hook-inverse-component-subnav();
@include hook-inverse-component-breadcrumb();
@include hook-inverse-component-pagination();
@include hook-inverse-component-tab();
@include hook-inverse-component-slidenav();
@include hook-inverse-component-dotnav();
@include hook-inverse-component-accordion();
@include hook-inverse-component-iconnav();
@include hook-inverse-component-text();
@include hook-inverse-component-column();
@include hook-inverse-component-utility();`;
/* First Step: Go through all files */
for (const file of await glob('src/less/**/*.less')) {
const data = await read(file);
/* replace all Less stuff with SCSS */
let scssData = data.replace(/\/less\//g, '/scss/') // change less/ dir to scss/ on imports
.replace(/\.less/g, '.scss') // change .less extensions to .scss on imports
.replace(/@/g, '$') // convert variables
.replace(/(:[^'"]*?\([^'"]+?)\s*\/\s*([0-9.-]+)\)/g, (exp, m1, m2) => `${m1} * ${NP.round(1 / parseFloat(m2), 5)})`)
.replace(/--uk-[^\s]+: (\$[^\s]+);/g, (exp, name) => exp.replace(name, `#{${name}}`))
.replace(/\\\$/g, '\\@') // revert classes using the @ symbol
.replace(/ e\(/g, ' unquote(') // convert escape function
.replace(/\.([\w-]*)\s*\((.*)\)\s*{/g, '@mixin $1($2){') // hook -> mixins
.replace(/(\$[\w-]*)\s*:(.*);/g, '$1: $2 !default;') // make variables optional
.replace(/@mixin ([\w-]*)\s*\((.*)\)\s*{\s*}/g, '// @mixin $1($2){}') // comment empty mixins
.replace(/\.(hook[a-zA-Z\-\d]+)(\(\))?;/g, '@if(mixin-exists($1)) {@include $1();}') // hook calls surrounded by a mixin-exists
.replace(/\$(import|supports|media|font-face|page|-ms-viewport|keyframes|-webkit-keyframes|-moz-document)/g, '@$1') // replace valid '@' statements
.replace(/tint\((\$[\w-]+),\s([^)]*)\)/g, 'mix(white, $1, $2)') // replace Less function tint with mix
.replace(/fade\((\$[\w-]*), ([0-9]+)%\)/g, (match, p1, p2) => { return `rgba(${p1}, ${p2 / 100})`;}) // replace Less function fade with rgba
.replace(/fadeout\((\$[\w-]*), ([0-9]+)%\)/g, (match, p1, p2) => { return `fade-out(${p1}, ${p2 / 100})`;}) // replace Less function fadeout with fade-out
.replace(/\.svg-fill/g, '@include svg-fill') // include svg-fill mixin
.replace(/(.*):extend\((\.[\w-\\@]*) all\) when \((\$[\w-]*) = ([\w]+)\) {}/g, '@if ( $3 == $4 ) { $1 { @extend $2 !optional;} }') // update conditional extend and add !optional to ignore warnings
.replace(/(\.[\w-\\@]+)\s*when\s*\((\$[\w-]*)\s*=\s*(\w+)\)\s*{\s*@if\(mixin-exists\(([\w-]*)\)\) {@include\s([\w-]*)\(\);\s*}\s*}/g, '@if ($2 == $3) { $1 { @if (mixin-exists($4)) {@include $4();}}}') // update conditional hook
.replace(/(\.[\w-\\@]+)\s*when\s*\((\$[\w-]*)\s*=\s*(\w+)\)\s*({\s*.*?\s*})/gs, '@if ($2 == $3) {\n$1 $4\n}') // replace conditionals
.replace(/\${/g, '#{$') // string literals: from: /~"(.*)"/g, to: '#{"$1"}'
.replace(/[^(](-\$[\w-]*)/g, ' ($1)') // surround negative variables with brackets
.replace(/(--[\w-]+:\s*)~'([^']+)'/g, '$1$2') // string literals in custom properties
.replace(/~('[^']+')/g, 'unquote($1)'); // string literals: for real
/* File name of the current file */
const [filename] = file.split('/').pop().split('.less');
if (filename !== 'inverse') {
scssData = scssData.replace(/hook-inverse(?!-)/g, `hook-inverse-component-${filename}`);
} else {
const joinedHook = `@mixin hook-inverse(){\n${inverseTemplate}\n}\n`;
scssData = scssData.replace(/\*\//, '*/\n' + joinedHook);
}
/* get all the mixins and remove them from the file */
scssData = getMixinsFromFile(file, scssData);
/* get all Variables but not from the mixin.less file */
if (filename !== 'mixin') {
scssData = await getVariablesFromFile(file, scssData);
}
if (filename === 'uikit.theme') {
/* remove the theme import first place */
scssData = scssData.replace(/\/\/\n\/\/ Theme\n\/\/\n\n@import "theme\/_import.scss";/, '');
/* add uikit-mixins and uikit-variables include to the uikit.scss file and change order, to load theme files first */
scssData = scssData.replace(/\/\/ Core\n\/\//g, '// Theme\n//\n\n@import "theme/_import.scss";');
}
/* mixin.less needs to be fully replaced by the new mixin file*/
if (filename === 'mixin') {
scssData = mixinTemplate;
}
await write(file.replace(/less/g, 'scss').replace('.theme.', '-theme.'), scssData);
}
/* Second Step write all new needed files for SASS */
/* write mixins into new file */
const mixins_theme = Object.keys(themeMixins).map(function (key) { return themeMixins[key]; });
await write('src/scss/mixins-theme.scss', mixins_theme.join('\n'));
const mixins_core = Object.keys(coreMixins).map(function (key) { return coreMixins[key]; });
await write('src/scss/mixins.scss', mixins_core.join('\n'));
/* write core variables */
const compactCoreVar = new Set();
Object.keys(coreVar).map(key => getAllDependencies(coreVar, key).forEach(dependency => compactCoreVar.add(dependency)));
await write('src/scss/variables.scss', Array.from(compactCoreVar).join('\n'));
/* write theme variables */
const compactThemeVar = new Set();
Object.keys(themeVar).map(key => getAllDependencies(themeVar, key).forEach(dependency => compactThemeVar.add(dependency)));
await write('src/scss/variables-theme.scss', Array.from(compactThemeVar).join('\n'));
/*
* recursive function to get a dependencie Set which is ordered so that no depencies exist to a later on entry
* @return Set with all the dependencies.
*/
function getAllDependencies(allVariables, currentKey, dependencies = new Set()) {
if (!allVariables[currentKey].dependencies.length) {
dependencies.add(`${currentKey}: ${allVariables[currentKey].value}`);
return Array.from(dependencies);
} else {
allVariables[currentKey].dependencies.forEach(dependecy => {
getAllDependencies(allVariables, dependecy, dependencies).forEach(newDependency => dependencies.add(newDependency));
});
dependencies.add(`${currentKey}: ${allVariables[currentKey].value}`);
return Array.from(dependencies);
}
}
/*
* function to extract all the mixins from a given file with its data.
* @return an updated data where the mixins have been removed.
*/
function getMixinsFromFile(file, data) {
/* Step 1: get all includes and insert them, so that at least empty mixins exist. */
let regex = /@include ([a-z0-9-]+)/g;
let match = regex.exec(data);
while (match) {
if (!(match[1] in themeMixins)) { themeMixins[match[1]] = `@mixin ${match[1]}(){}`; }
if (!(match[1] in coreMixins)) { coreMixins[match[1]] = `@mixin ${match[1]}(){}`; }
match = regex.exec(data);
}
/* Step 2: get all multiline mixins */
regex = /@mixin ([\w-]*)\s*\((.*)\)\s*{\n(\s+[\w\W]+?)(?=\n})\n}/g;
match = regex.exec(data);
while (match) {
[themeMixins[match[1]]] = match;
if (file.indexOf('theme/') < 0) {
[coreMixins[match[1]]] = match;
}
match = regex.exec(data);
}
/* Step 3: get all singleline mixins */
regex = /@mixin ([\w-]*)\s*\((.*)\)\s*{( [^\n]+)}/g;
match = regex.exec(data);
while (match) {
[themeMixins[match[1]]] = match;
if (file.indexOf('theme/') < 0) {
[coreMixins[match[1]]] = match;
}
match = regex.exec(data);
}
/* Step 4: remove the mixins from the file, so that users can overwrite them in their custom code. */
return data
.replace(/@mixin ([\w-]*)\s*\((.*)\)\s*{\n(\s+[\w\W]+?)(?=\n})\n}/g, '')
.replace(/@mixin ([\w-]*)\s*\((.*)\)\s*{( [^\n]+)}/g, '');
}
/*
* function to extract all the variables from a given file with its data.
* @return an updated data where the icons have been replaced by the actual SVG data.
*/
async function getVariablesFromFile(file, data) {
const regex = /(\$[\w-]*)\s*:\s*(.*);/g;
let match = regex.exec(data);
while (match) {
/* check if variable is a background icon, if so replace it directly by the SVG */
if (match[0].indexOf('../../images/backgrounds') >= 0) {
const iconregex = /(\$[\w-]+)\s*:\s*"\.\.\/\.\.\/images\/backgrounds\/([\w./-]+)" !default;/g;
const iconmatch = iconregex.exec(match[0]);
let svg = (await read(`src/images/backgrounds/${iconmatch[2]}`)).toString();
svg = `"${svg.replace(/\r?\n|\r/g, '%0A')
.replace(/"/g, '\'')
.replace(/\s/g, '%20')
.replace(/</g, '%3C')
.replace(/=/g, '%3D')
.replace(/'/g, '%22')
.replace(/:/g, '%3A')
.replace(/\//g, '%2F')
.replace(/>/g, '%3E')
.replace(/%3Csvg/, 'data:image/svg+xml;charset=UTF-8,%3Csvg')}"`;
/* add SVG to the coreVar and themeVar only if it is a theme file and make it optional */
if (file.indexOf('theme/') < 0) {
coreVar[iconmatch[1]] = {value: `${svg} !default;`, dependencies: []};
}
themeVar[iconmatch[1]] = {value: `${svg} !default;`, dependencies: []};
/* add SVG to the variable within the file itself as well */
const inlineSVG = `${iconmatch[1]}: ${svg} !default;`;
data = data.replace(match[0], inlineSVG);
/* when it is not an SVG add the variable and search for its dependencies */
} else {
const variablesRegex = /(\$[\w-]+)/g;
let variablesMatch = variablesRegex.exec(match[2]);
const dependencies = [];
while (variablesMatch) {
dependencies.push(variablesMatch[1]);
variablesMatch = variablesRegex.exec(match[2]);
}
/* add variables only to the core Variables if it is not a theme file */
if (file.indexOf('theme/') < 0) {
coreVar[match[1]] = {value: `${match[2]};`, dependencies: Array.from(dependencies)};
}
themeVar[match[1]] = {value: `${match[2]};`, dependencies: Array.from(dependencies)};
}
match = regex.exec(data);
}
return data;
}

246
client/uikit/build/util.js Normal file
View File

@ -0,0 +1,246 @@
import less from 'less';
import fs from 'fs-extra';
import pLimit from 'p-limit';
import globImport from 'glob';
import {optimize} from 'svgo';
import {promisify} from 'util';
import minimist from 'minimist';
import CleanCSS from 'clean-css';
import html from 'rollup-plugin-html';
import buble from '@rollup/plugin-buble';
import alias from '@rollup/plugin-alias';
import modify from 'rollup-plugin-modify';
import replace from '@rollup/plugin-replace';
import {basename, dirname, join} from 'path';
import {exec as execImport} from 'child_process';
import {rollup, watch as rollupWatch} from 'rollup';
import {minify as rollupMinify} from 'rollup-plugin-esbuild';
const limit = pLimit(Number(process.env.cpus || 2));
export const exec = promisify(execImport);
export const glob = promisify(globImport);
export const {pathExists, readJson} = fs;
export const banner = `/*! UIkit ${await getVersion()} | https://www.getuikit.com | (c) 2014 - ${new Date().getFullYear()} YOOtheme | MIT License */\n`;
export const validClassName = /[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/;
const argv = minimist(process.argv.slice(2));
argv._.forEach(arg => {
const tokens = arg.split('=');
argv[tokens[0]] = tokens[1] || true;
});
export const args = argv;
export function read(file) {
return fs.readFile(file, 'utf8');
}
export async function write(dest, data) {
const err = await fs.writeFile(dest, data);
if (err) {
console.log(err);
throw err;
}
await logFile(dest);
return dest;
}
export async function logFile(file) {
const data = await read(file);
console.log(`${cyan(file)} ${getSize(data)}`);
}
export async function minify(file) {
const {styles} = await limit(() => new CleanCSS({
advanced: false,
keepSpecialComments: 0,
rebase: false,
returnPromise: true
}).minify([file]));
await write(`${join(dirname(file), basename(file, '.css'))}.min.css`, styles);
return styles;
}
export function renderLess(data, options) {
return limit(async () => (await less.render(data, options)).css);
}
export async function compile(file, dest, {external, globals, name, aliases, replaces} = {}) {
const minify = !args.nominify;
const debug = args.d || args.debug;
const watch = args.w || args.watch;
name = (name || '').replace(/[^\w]/g, '_');
const inputOptions = {
external,
input: file,
plugins: [
replace({
preventAssignment: true,
values: {
VERSION: `'${await getVersion()}'`,
...replaces
}
}),
alias({
entries: {
'uikit-util': './src/js/util/index.js',
...aliases
}
}),
html({
include: '**/*.svg',
htmlMinifierOptions: {
collapseWhitespace: true
}
}),
buble({namedFunctionExpressions: false}),
modify({
find: /(>)\\n\s+|\\n\s+(<)/,
replace: (m, m1, m2) => `${m1 || ''} ${m2 || ''}`
})
]
};
const outputOptions = {
globals,
banner,
format: 'umd',
amd: {id: `UIkit${name}`.toLowerCase()},
name: `UIkit${ucfirst(name)}`,
sourcemap: debug ? 'inline' : false
};
const output = [{
...outputOptions,
file: `${dest}.js`
}];
if (minify) {
output.push({
...outputOptions,
file: `${dest}.min.js`,
plugins: [minify ? rollupMinify() : undefined]
});
}
if (!watch) {
const bundle = await rollup(inputOptions);
for (const options of output) {
await limit(() => bundle.write(options));
logFile(options.file);
}
await bundle.close();
} else {
console.log('UIkit is watching the files...');
const watcher = rollupWatch({
...inputOptions,
output
});
watcher.on('event', ({code, result, output, error}) => {
if (result) {
result.close();
}
if (code === 'BUNDLE_END' && output) {
output.map(logFile);
}
if (error) {
console.error(error);
}
});
watcher.close();
}
}
export async function icons(src) {
const options = {
plugins: [
{
name: 'preset-default',
params: {
overrides: {
removeViewBox: false,
cleanupNumericValues: {
floatPrecision: 3
},
convertPathData: false,
convertShapeToPath: false,
mergePaths: false,
removeDimensions: false,
removeStyleElement: false,
removeScriptElement: false,
removeUnknownsAndDefaults: false,
removeUselessStrokeAndFill: false
}
}
}
]
};
const files = await glob(src, {nosort: true});
const icons = await Promise.all(
files.map(file =>
limit(async () =>
(await optimize(await read(file), options)).data
)
)
);
return JSON.stringify(files.reduce((result, file, i) => {
result[basename(file, '.svg')] = icons[i];
return result;
}, {}), null, ' ');
}
export async function run(cmd, options) {
const {stdout, stderr} = await limit(() => exec(cmd, options));
stdout && console.log(stdout.trim());
stderr && console.log(stderr.trim());
return stdout;
}
export function ucfirst(str) {
return str.length ? str.charAt(0).toUpperCase() + str.slice(1) : '';
}
export async function getVersion() {
return (await readJson('package.json')).version;
}
export async function replaceInFile(file, fn) {
await write(file, await fn(await read(file)));
}
function cyan(str) {
return `\x1b[1m\x1b[36m${str}\x1b[39m\x1b[22m`;
}
function getSize(data) {
return `${(data.length / 1024).toFixed(2)}kb`;
}

View File

@ -0,0 +1,7 @@
import Component from 'component';
if (typeof window !== 'undefined' && window.UIkit) {
window.UIkit.component(NAME, Component);
}
export default Component;

View File

@ -0,0 +1,15 @@
function plugin(UIkit) {
if (plugin.installed) {
return;
}
UIkit.icon.add(ICONS);
}
if (typeof window !== 'undefined' && window.UIkit) {
window.UIkit.use(plugin);
}
export default plugin;

View File

@ -11,7 +11,7 @@ kotlin {
commonMain {
dependencies {
api project(":postssystem.features.common.common")
api "dev.inmo:micro_utils.repos.ktor.client:$microutils_version"
api libs.microutils.repos.ktor.client
api "io.ktor:ktor-client-auth:$ktor_version"
api "io.ktor:ktor-client-logging:$ktor_version"
}

View File

@ -10,8 +10,8 @@ kotlin {
sourceSets {
commonMain {
dependencies {
api "dev.inmo:micro_utils.common:$microutils_version"
api "dev.inmo:micro_utils.serialization.typed_serializer:$microutils_version"
api libs.microutils.common
api libs.microutils.serialization.typedserializer
api "io.insert-koin:koin-core:$koin_version"
api "com.benasher44:uuid:$uuid_version"
api "com.soywiz.korlibs.klock:klock:$klock_version"

View File

@ -0,0 +1,52 @@
String versionLine() { return " const val project = \"$project.version\"" }
File versionFile() { return project.file("src/commonMain/kotlin/dev/inmo/postssystem/features/common/common/Version.kt") }
void createVersionFile() {
String versionLine = versionLine()
File versionFile = versionFile()
versionFile.parentFile.mkdirs()
if (versionFile.exists()) versionFile.delete()
versionFile.createNewFile()
versionFile.text = """package dev.inmo.postssystem.features.common.common
/*
* THIS FILE HAS BEEN CREATED AUTOMATICALLY.
* DO NOT EDIT THIS FILE, ITS CONTENT WILL BE OVERWRITTEN
* WITH ANY NEW BUILD
*/
object Version {
$versionLine
}
"""
}
Boolean isVersionFileUpToDate() {
String versionLine = versionLine()
File versionFile = versionFile()
boolean upToDate = false
if (versionFile.exists()) {
versionFile.withReader { reader ->
String line
while ((line = reader.readLine()) != null && !upToDate) {
upToDate = line == versionLine
}
}
}
return upToDate
}
task createVersionFile {
doLast {
createVersionFile()
}
outputs.upToDateWhen {
isVersionFileUpToDate()
}
}
if (!isVersionFileUpToDate()) {
createVersionFile()
}

View File

@ -10,9 +10,9 @@ kotlin {
commonMain {
dependencies {
api project(":postssystem.features.common.common")
api "dev.inmo:micro_utils.repos.exposed:$microutils_version"
api "dev.inmo:micro_utils.repos.ktor.server:$microutils_version"
api "dev.inmo:micro_utils.ktor.server:$microutils_version"
api libs.microutils.repos.exposed
api libs.microutils.repos.ktor.server
api libs.microutils.ktor.server
}
}
jvmMain {

View File

@ -12,7 +12,7 @@ kotlin {
dependencies {
api project(":postssystem.features.common.common")
api project(":postssystem.features.content.common")
api "dev.inmo:micro_utils.mime_types:$microutils_version"
api libs.microutils.mimetypes
}
}
}

View File

@ -11,7 +11,7 @@ kotlin {
commonMain {
dependencies {
api project(":postssystem.features.common.common")
api "dev.inmo:micro_utils.mime_types:$microutils_version"
api libs.microutils.mimetypes
}
}
}

View File

@ -11,8 +11,8 @@ kotlin {
commonMain {
dependencies {
api project(":postssystem.features.common.common")
api "dev.inmo:micro_utils.mime_types:$microutils_version"
api "dev.inmo:micro_utils.repos.common:$microutils_version"
api libs.microutils.mimetypes
api libs.microutils.repos.common
}
}
}

View File

@ -11,12 +11,12 @@ kotlin {
commonMain {
dependencies {
api project(":postssystem.features.common.common")
api "dev.inmo:micro_utils.repos.common:$microutils_version"
api libs.microutils.repos.common
}
}
jvmMain {
dependencies {
api "dev.inmo:micro_utils.repos.exposed:$microutils_version"
api libs.microutils.repos.exposed
}
}
}

View File

@ -13,17 +13,14 @@ kotlin_version=1.6.10
kotlin_serialisation_core_version=1.3.2
koin_version=3.1.2
microutils_version=0.9.4
ktor_version=1.6.7
logback_version=1.2.10
uuid_version=0.3.1
klock_version=2.4.12
tgbotapi_version=0.38.3
klock_version=2.5.2
# Server
kotlin_exposed_version=0.37.2
kotlin_exposed_version=0.37.3
psql_version=42.3.0
# JS

View File

@ -1,13 +1,35 @@
[versions]
jsuikit = "0.0.27"
compose = "1.0.1"
microutils = "0.9.4"
kotlin = "1.6.10"
kotlin-serialization = "1.3.2"
jsuikit = "0.0.41"
compose = "1.1.0"
microutils = "0.9.9"
tgbotapi = "0.38.6"
ktor = "1.6.7"
exposed = "0.37.3"
psql = "42.3.0"
android-dexcount = "3.0.1"
android-junit = "4.12"
android-test-junit = "1.1.2"
android-espresso-core = "3.3.0"
[libraries]
kotlin-std = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
kotlin-serialization = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlin-serialization" }
kotlin-serialization-properties = { module = "org.jetbrains.kotlinx:kotlinx-serialization-properties", version.ref = "kotlin-serialization" }
jsuikit = { module = "dev.inmo:kjsuikit", version.ref = "jsuikit" }
postgresql = { module = "org.postgresql:postgresql", version.ref = "psql" }
exposed-jdbs = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "exposed" }
ktor-server-netty = { module = "io.ktor:ktor-server-netty", version.ref = "ktor" }
ktor-websockets = { module = "io.ktor:ktor-websockets", version.ref = "ktor" }
microutils-common = { module = "dev.inmo:micro_utils.common", version.ref = "microutils" }
microutils-pagination-common = { module = "dev.inmo:micro_utils.pagination.common", version.ref = "microutils" }
microutils-fsm-common = { module = "dev.inmo:micro_utils.fsm.common", version.ref = "microutils" }
@ -19,8 +41,14 @@ microutils-repos-ktor-server = { module = "dev.inmo:micro_utils.repos.ktor.serve
microutils-repos-exposed = { module = "dev.inmo:micro_utils.repos.exposed", version.ref = "microutils" }
microutils-mimetypes = { module = "dev.inmo:micro_utils.mime_types", version.ref = "microutils" }
microutils-coroutines = { module = "dev.inmo:micro_utils.coroutines", version.ref = "microutils" }
microutils-ktor-server = { module = "dev.inmo:micro_utils.ktor.server", version.ref = "microutils" }
microutils-serialization-typedserializer = { module = "dev.inmo:micro_utils.serialization.typed_serializer", version.ref = "microutils" }
tgbotapi = { module = "dev.inmo:tgbotapi", version.ref = "tgbotapi" }
androidx-test-junit = { module = "androidx.test.ext:junit", version.ref = "android-test-junit" }
androidx-espresso = { module = "androidx.test.espresso:espresso-core", version.ref = "android-espresso-core" }
[plugins]
compose = { id = "org.jetbrains.compose", version.ref = "compose" }

View File

@ -1,4 +1,4 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip
zipStoreBase=GRADLE_USER_HOME

View File

@ -20,6 +20,13 @@ kotlin {
implementation kotlin('test-annotations-common')
}
}
androidTest {
dependencies {
implementation kotlin('test-junit')
implementation libs.androidx.test.junit
implementation libs.androidx.espresso
}
}
}
}

View File

@ -16,8 +16,8 @@ kotlin {
sourceSets {
commonMain {
dependencies {
implementation kotlin('stdlib')
api "org.jetbrains.kotlinx:kotlinx-serialization-json:$kotlin_serialisation_core_version"
implementation libs.kotlin.std
api libs.kotlin.serialization
}
}
commonTest {
@ -40,8 +40,8 @@ kotlin {
androidTest {
dependencies {
implementation kotlin('test-junit')
implementation "androidx.test.ext:junit:$test_ext_junit_version"
implementation "androidx.test.espresso:espresso-core:$espresso_core"
implementation libs.androidx.test.junit
implementation libs.androidx.espresso
}
}
}

26
prepare_client.sh Executable file
View File

@ -0,0 +1,26 @@
#!/bin/bash
function assert_success() {
"${@}"
local status=${?}
if [ ${status} -ne 0 ]; then
send_notification "### Error ${status} at: ${BASH_LINENO[*]} ###"
exit ${status}
fi
}
rootPath="`pwd`"
localPath="$rootPath/local/"
mkdir "$localPath"
assert_success cd "$localPath"
assert_success git clone https://github.com/uikit/uikit.git
assert_success cd uikit
assert_success git checkout 4f815b6482fb93c98452c857b7a9a1450fecd660 # https://github.com/uikit/uikit/releases/tag/v3.11.1
assert_success cd -
assert_success cp -r "uikit/dist" "uikit/build" "uikit/tests" "$rootPath/client/uikit/"
assert_success cd "$rootPath"
rm -rf "$localPath/uikit"

View File

@ -27,10 +27,10 @@ dependencies {
api project(":postssystem.targets.telegram.publication.server")
api "io.ktor:ktor-server-netty:$ktor_version"
api "io.ktor:ktor-websockets:$ktor_version"
api "org.jetbrains.exposed:exposed-jdbc:$kotlin_exposed_version"
api "org.postgresql:postgresql:$psql_version"
api libs.ktor.server.netty
api libs.ktor.websockets
api libs.exposed.jdbs
api libs.postgresql
}

View File

@ -12,8 +12,8 @@ kotlin {
dependencies {
api project(":postssystem.features.common.common")
api project(":postssystem.features.posts.common")
api "dev.inmo:micro_utils.repos.common:$microutils_version"
api "dev.inmo:micro_utils.repos.ktor.client:$microutils_version"
api libs.microutils.repos.common
api libs.microutils.repos.ktor.client
}
}
}

View File

@ -14,8 +14,8 @@ kotlin {
api project(":postssystem.features.content.server")
api project(":postssystem.features.posts.server")
api project(":postssystem.features.users.server")
api "dev.inmo:micro_utils.common:$microutils_version"
api "org.jetbrains.kotlinx:kotlinx-serialization-properties:$kotlin_serialisation_core_version"
api libs.microutils.common
api libs.kotlin.serialization.properties
}
}
}

View File

@ -15,7 +15,7 @@ kotlin {
api project(":postssystem.features.content.binary.server")
api project(":postssystem.features.content.text.server")
api "dev.inmo:tgbotapi:$tgbotapi_version"
api libs.tgbotapi
}
}
}